aboutsummaryrefslogtreecommitdiffstats
path: root/Malmstone
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2024-08-27 22:21:18 -0700
committerPinapelz <yukais@pinapelz.com>2024-08-27 22:21:18 -0700
commit72f85fd952c8e53230c968ef9a433644dae90254 (patch)
treea93b73e9d045243cdc4ee467376ef8573f297d2c /Malmstone
parentb8c43fbe717d794284c6c4578c9c00ae8e26d711 (diff)
Initial Version 1.0.0.0
Diffstat (limited to 'Malmstone')
-rw-r--r--Malmstone/Configuration.cs19
-rw-r--r--Malmstone/Dalamud.Plugin.Bootstrap.targets11
-rw-r--r--Malmstone/Malmstone.csproj12
-rw-r--r--Malmstone/Malmstone.json13
-rw-r--r--Malmstone/Plugin.cs63
-rw-r--r--Malmstone/Services/PVPService.cs32
-rw-r--r--Malmstone/Utils/MalmstoneXPCalculator.cs107
-rw-r--r--Malmstone/Windows/ConfigWindow.cs44
-rw-r--r--Malmstone/Windows/MainWindow.cs136
-rw-r--r--Malmstone/packages.lock.json13
10 files changed, 450 insertions, 0 deletions
diff --git a/Malmstone/Configuration.cs b/Malmstone/Configuration.cs
new file mode 100644
index 0000000..e0d5a3c
--- /dev/null
+++ b/Malmstone/Configuration.cs
@@ -0,0 +1,19 @@
+using Dalamud.Configuration;
+using Dalamud.Plugin;
+using System;
+
+namespace Malmstone;
+
+[Serializable]
+public class Configuration : IPluginConfiguration
+{
+ public int Version { get; set; } = 0;
+
+ public int DefaultTargetRankProperty { get; set; } = 1;
+
+ // the below exist just to make saving less cumbersome
+ public void Save()
+ {
+ Plugin.PluginInterface.SavePluginConfig(this);
+ }
+}
diff --git a/Malmstone/Dalamud.Plugin.Bootstrap.targets b/Malmstone/Dalamud.Plugin.Bootstrap.targets
new file mode 100644
index 0000000..11eec9c
--- /dev/null
+++ b/Malmstone/Dalamud.Plugin.Bootstrap.targets
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project>
+ <PropertyGroup>
+ <DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('Windows'))">$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
+ <DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('Linux'))">$(HOME)/.xlcore/dalamud/Hooks/dev/</DalamudLibPath>
+ <DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('OSX'))">$(HOME)/Library/Application Support/XIV on Mac/dalamud/Hooks/dev/</DalamudLibPath>
+ <DalamudLibPath Condition="$(DALAMUD_HOME) != ''">$(DALAMUD_HOME)/</DalamudLibPath>
+ </PropertyGroup>
+
+ <Import Project="$(DalamudLibPath)/targets/Dalamud.Plugin.targets"/>
+</Project>
diff --git a/Malmstone/Malmstone.csproj b/Malmstone/Malmstone.csproj
new file mode 100644
index 0000000..dbc432f
--- /dev/null
+++ b/Malmstone/Malmstone.csproj
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project Sdk="Microsoft.NET.Sdk">
+ <Import Project="Dalamud.Plugin.Bootstrap.targets"/>
+
+ <PropertyGroup>
+ <Version>1.0.0.0</Version>
+ <Description>A PVP Series Malmstones Experience Calculator</Description>
+ <PackageProjectUrl>https://github.com/goatcorp/Malmstone</PackageProjectUrl>
+ <PackageLicenseExpression>AGPL-3.0-or-later</PackageLicenseExpression>
+ <IsPackable>false</IsPackable>
+ </PropertyGroup>
+</Project>
diff --git a/Malmstone/Malmstone.json b/Malmstone/Malmstone.json
new file mode 100644
index 0000000..d93fcd7
--- /dev/null
+++ b/Malmstone/Malmstone.json
@@ -0,0 +1,13 @@
+{
+ "Author": "pinapelz",
+ "Name": "Malmstone Calculator",
+ "Punchline": "A PVP Series Malmstones Experience Calculator",
+ "Description": "Calculate how much more PVP you have to play before reaching a certain rank in the current PVP Series",
+ "ApplicableVersion": "any",
+ "Tags": [
+ "pvp",
+ "malmstone",
+ "series",
+ "calculator"
+ ]
+}
diff --git a/Malmstone/Plugin.cs b/Malmstone/Plugin.cs
new file mode 100644
index 0000000..0455792
--- /dev/null
+++ b/Malmstone/Plugin.cs
@@ -0,0 +1,63 @@
+using Dalamud.Game.Command;
+using Dalamud.IoC;
+using Dalamud.Plugin;
+using Dalamud.Interface.Windowing;
+using Dalamud.Plugin.Services;
+using Malmstone.Windows;
+
+namespace Malmstone;
+
+public sealed class Plugin : IDalamudPlugin
+{
+ [PluginService] internal static IDalamudPluginInterface PluginInterface { get; private set; } = null!;
+ [PluginService] internal static ITextureProvider TextureProvider { get; private set; } = null!;
+ [PluginService] internal static ICommandManager CommandManager { get; private set; } = null!;
+
+ private const string CommandName = "/pmalm";
+
+ public Configuration Configuration { get; init; }
+
+ public readonly WindowSystem WindowSystem = new("Malmstone");
+ private ConfigWindow ConfigWindow { get; init; }
+ private MainWindow MainWindow { get; init; }
+
+ public Plugin()
+ {
+ Configuration = PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
+
+ ConfigWindow = new ConfigWindow(this);
+ MainWindow = new MainWindow(this);
+
+ WindowSystem.AddWindow(ConfigWindow);
+ WindowSystem.AddWindow(MainWindow);
+
+ CommandManager.AddHandler(CommandName, new CommandInfo(OnCommand)
+ {
+ HelpMessage = "Open the Malmstone calculator main window"
+ });
+
+ PluginInterface.UiBuilder.Draw += DrawUI;
+ PluginInterface.UiBuilder.OpenConfigUi += ToggleConfigUI;
+ PluginInterface.UiBuilder.OpenMainUi += ToggleMainUI;
+ }
+
+ public void Dispose()
+ {
+ WindowSystem.RemoveAllWindows();
+
+ ConfigWindow.Dispose();
+ MainWindow.Dispose();
+
+ CommandManager.RemoveHandler(CommandName);
+ }
+
+ private void OnCommand(string command, string args)
+ {
+ ToggleMainUI();
+ }
+
+ private void DrawUI() => WindowSystem.Draw();
+
+ public void ToggleConfigUI() => ConfigWindow.Toggle();
+ public void ToggleMainUI() => MainWindow.Toggle();
+}
diff --git a/Malmstone/Services/PVPService.cs b/Malmstone/Services/PVPService.cs
new file mode 100644
index 0000000..cf08fea
--- /dev/null
+++ b/Malmstone/Services/PVPService.cs
@@ -0,0 +1,32 @@
+using FFXIVClientStructs.FFXIV.Client.Game.UI;
+
+namespace Malmstone.Services
+{
+ public class PvPService
+ {
+ public PvPSeriesInfo? GetPvPSeriesInfo()
+ {
+ unsafe
+ {
+ var pvpProfile = PvPProfile.Instance();
+ if (pvpProfile != null && pvpProfile->IsLoaded != 0)
+ {
+ return new PvPSeriesInfo
+ {
+ CurrentSeriesRank = pvpProfile->GetSeriesCurrentRank(),
+ ClaimedSeriesRank = pvpProfile->GetSeriesClaimedRank(),
+ SeriesExperience = pvpProfile->GetSeriesExperience()
+ };
+ }
+ return null;
+ }
+ }
+ }
+
+ public class PvPSeriesInfo
+ {
+ public byte CurrentSeriesRank { get; set; }
+ public byte ClaimedSeriesRank { get; set; }
+ public ushort SeriesExperience { get; set; }
+ }
+}
diff --git a/Malmstone/Utils/MalmstoneXPCalculator.cs b/Malmstone/Utils/MalmstoneXPCalculator.cs
new file mode 100644
index 0000000..4834def
--- /dev/null
+++ b/Malmstone/Utils/MalmstoneXPCalculator.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+
+namespace Malmstone.Utils
+{
+ public static class MalmstoneXPCalculator
+ {
+ private static readonly int[] PvpLevels = {
+ 0, 2000, 2000, 2000, 2000, 3000, 3000, 3000, 3000, 3000, 4000, 4000, 4000, 4000, 4000, 5500, 5500, 5500, 5500, 5500,
+ 7500, 7500, 7500, 7500, 7500, 10000, 10000, 10000, 10000, 10000, 20000, 20000
+ };
+
+ private const int InfinityLevelExp = 20000;
+ private const int FrontlineWinExp = 1500;
+ private const int FrontlineLose2Exp = 1250;
+ private const int FrontlineLoseExp = 1000;
+ private const int FrontlineDailyWinExp = 3000;
+ private const int FrontlineDailyLose2Exp = 2750;
+ private const int FrontlineDailyLoseExp = 2500;
+ private const int CrystallineWinExp = 900;
+ private const int CrystallineLoseExp = 700;
+ private const int RivalWingsWinExp = 1250;
+ private const int RivalWingsLoseExp = 750;
+
+ public class XpCalculationResult
+ {
+ public int RemainingXp { get; set; }
+ public int TargetLevel { get; set; }
+ public Dictionary<string, int> ActivityCounts { get; set; } = new();
+ }
+
+ public static XpCalculationResult CalculateXp(int currentLevel, int goalLevel, int currentProgress)
+ {
+ if (currentLevel < 1 || goalLevel < 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(currentLevel), "Levels must be greater than 0.");
+ }
+
+ int remainingXp = 0;
+
+ if (currentLevel <= PvpLevels.Length && goalLevel <= PvpLevels.Length)
+ {
+ remainingXp = CalculateRemainingXp(currentLevel, goalLevel, currentProgress);
+ }
+ else
+ {
+ remainingXp = CalculateRemainingXpBeyondChart(currentLevel, goalLevel, currentProgress);
+ }
+
+ if (remainingXp <= 0)
+ {
+ return new XpCalculationResult { RemainingXp = 0, TargetLevel = goalLevel };
+ }
+
+ var result = new XpCalculationResult
+ {
+ RemainingXp = remainingXp,
+ TargetLevel = goalLevel,
+ };
+
+ result.ActivityCounts["Crystalline Conflict Win"] = CalculateActivityCount(remainingXp, CrystallineWinExp);
+ result.ActivityCounts["Crystalline Conflict Lose"] = CalculateActivityCount(remainingXp, CrystallineLoseExp);
+ result.ActivityCounts["Frontline Win"] = CalculateActivityCount(remainingXp, FrontlineWinExp);
+ result.ActivityCounts["Frontline Lose 2nd"] = CalculateActivityCount(remainingXp, FrontlineLose2Exp);
+ result.ActivityCounts["Frontline Lose 3rd"] = CalculateActivityCount(remainingXp, FrontlineLoseExp);
+ result.ActivityCounts["Frontline Daily Win"] = CalculateActivityCount(remainingXp, FrontlineDailyWinExp);
+ result.ActivityCounts["Frontline Daily Lose 2nd"] = CalculateActivityCount(remainingXp, FrontlineDailyLose2Exp);
+ result.ActivityCounts["Frontline Daily Lose 3rd"] = CalculateActivityCount(remainingXp, FrontlineDailyLoseExp);
+ result.ActivityCounts["Rival Wings Win"] = CalculateActivityCount(remainingXp, RivalWingsWinExp);
+ result.ActivityCounts["Rival Wings Lose"] = CalculateActivityCount(remainingXp, RivalWingsLoseExp);
+
+ return result;
+ }
+
+ private static int CalculateRemainingXp(int currentLevel, int goalLevel, int currentProgress)
+ {
+ int remainingXp = 0;
+
+ for (int level = currentLevel; level < goalLevel; level++)
+ {
+ remainingXp += PvpLevels[level];
+ }
+
+ return remainingXp - currentProgress;
+ }
+
+ private static int CalculateRemainingXpBeyondChart(int currentLevel, int goalLevel, int currentProgress)
+ {
+ int remainingXp = 0;
+
+ if (currentLevel <= PvpLevels.Length)
+ {
+ remainingXp = CalculateRemainingXp(currentLevel, PvpLevels.Length, currentProgress);
+ currentLevel = PvpLevels.Length;
+ }
+
+ remainingXp += (goalLevel - currentLevel) * InfinityLevelExp;
+
+ return remainingXp;
+ }
+
+ private static int CalculateActivityCount(int remainingXp, int activityXp)
+ {
+ return (int)Math.Ceiling((double)remainingXp / activityXp);
+ }
+ }
+}
diff --git a/Malmstone/Windows/ConfigWindow.cs b/Malmstone/Windows/ConfigWindow.cs
new file mode 100644
index 0000000..8fbe9ba
--- /dev/null
+++ b/Malmstone/Windows/ConfigWindow.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Numerics;
+using Dalamud.Interface.Windowing;
+using ImGuiNET;
+
+namespace Malmstone.Windows;
+
+public class ConfigWindow : Window, IDisposable
+{
+ private Configuration Configuration;
+
+ public ConfigWindow(Plugin plugin) : base("Malmstone Config")
+ {
+ Flags = ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar |
+ ImGuiWindowFlags.NoScrollWithMouse;
+
+ Size = new Vector2(232, 150);
+ Configuration = plugin.Configuration;
+ }
+
+ public void Dispose() { }
+
+ public override void PreDraw()
+ {
+ }
+
+ public override void Draw()
+ {
+ ImGui.Text("Default Series Rank");
+ var savedTargetSeriesRank = Configuration.DefaultTargetRankProperty;
+ if (ImGui.InputInt("##SavedTargetSeriesRank", ref savedTargetSeriesRank, 1))
+ {
+ Configuration.DefaultTargetRankProperty = savedTargetSeriesRank;
+ }
+
+ ImGui.Spacing();
+
+ if (ImGui.Button("Save and Close"))
+ {
+ Configuration.Save();
+ IsOpen = false;
+ }
+ }
+}
diff --git a/Malmstone/Windows/MainWindow.cs b/Malmstone/Windows/MainWindow.cs
new file mode 100644
index 0000000..917dd64
--- /dev/null
+++ b/Malmstone/Windows/MainWindow.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Numerics;
+using Dalamud.Interface.Windowing;
+using ImGuiNET;
+using Malmstone.Services;
+using Malmstone.Utils;
+
+namespace Malmstone.Windows
+{
+ public class MainWindow : Window, IDisposable
+ {
+ private Plugin Plugin;
+ private PvPService PvPService;
+ private int TargetSeriesRank;
+
+ public MainWindow(Plugin plugin)
+ : base("Malmstone", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)
+ {
+ SizeConstraints = new WindowSizeConstraints
+ {
+ MinimumSize = new Vector2(375, 310),
+ MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
+ };
+
+ Plugin = plugin;
+ PvPService = new PvPService();
+ TargetSeriesRank = Plugin.Configuration.DefaultTargetRankProperty;
+ }
+
+ public void Dispose() { }
+
+ public override void Draw()
+ {
+ var pvpInfo = PvPService.GetPvPSeriesInfo();
+ if (pvpInfo != null)
+ {
+ ImGui.Text($"Current Series Rank: {pvpInfo.CurrentSeriesRank}");
+ ImGui.Text($"Current Rank Series Experience: {pvpInfo.SeriesExperience}");
+ if (pvpInfo.CurrentSeriesRank != pvpInfo.ClaimedSeriesRank)
+ {
+ ImGui.Text("Don't forget to claim your rank rewards!");
+
+ }
+ ImGui.Spacing();
+
+ ImGui.Text("Target Rank:");
+ ImGui.InputInt("##TargetSeriesRank", ref TargetSeriesRank, 1);
+ }
+ else
+ {
+ ImGui.Text("PvP Profile is not loaded.");
+ }
+
+ if (TargetSeriesRank < 1) TargetSeriesRank = 1;
+
+ ImGui.Spacing();
+ ImGui.Separator();
+
+ if (pvpInfo != null)
+ {
+ var xpResult = MalmstoneXPCalculator.CalculateXp(pvpInfo.CurrentSeriesRank, TargetSeriesRank, pvpInfo.SeriesExperience);
+
+ ImGui.Spacing();
+ ImGui.Text($"You have {xpResult.RemainingXp} remaining series EXP to go until you reach rank {xpResult.TargetLevel}");
+
+ ImGui.Spacing();
+ ImGui.Separator();
+
+ // Crystalline Conflict Section
+ ImGui.TextColored(new Vector4(0.6f, 0.8f, 1f, 1f), "Crystalline Conflict");
+ ImGui.Spacing();
+ if (xpResult.ActivityCounts.ContainsKey("Crystalline Conflict Win"))
+ {
+ ImGui.BulletText($"Win: {xpResult.ActivityCounts["Crystalline Conflict Win"]} times");
+ }
+ if (xpResult.ActivityCounts.ContainsKey("Crystalline Conflict Lose"))
+ {
+ ImGui.BulletText($"Lose: {xpResult.ActivityCounts["Crystalline Conflict Lose"]} times");
+ }
+
+ ImGui.Spacing();
+ ImGui.Separator();
+
+ // Frontlines Section
+ ImGui.TextColored(new Vector4(0.8f, 0.6f, 0.6f, 1f), "Frontlines");
+ ImGui.Spacing();
+ if (xpResult.ActivityCounts.ContainsKey("Frontline Win"))
+ {
+ ImGui.BulletText($"Take 1st Place: {xpResult.ActivityCounts["Frontline Win"]} times");
+ }
+ if (xpResult.ActivityCounts.ContainsKey("Frontline Lose 2nd"))
+ {
+ ImGui.BulletText($"Take 2nd Place: {xpResult.ActivityCounts["Frontline Lose 2nd"]} times");
+ }
+ if (xpResult.ActivityCounts.ContainsKey("Frontline Lose 3rd"))
+ {
+ ImGui.BulletText($"Take 3rd Place: {xpResult.ActivityCounts["Frontline Lose 3rd"]} times");
+ }
+
+ // Frontlines Section
+ ImGui.TextColored(new Vector4(0.8f, 0.6f, 0.6f, 1f), "Frontlines (Roulette)");
+ ImGui.Spacing();
+ if (xpResult.ActivityCounts.ContainsKey("Frontline Daily Win"))
+ {
+ ImGui.BulletText($"Take 1st Place: {xpResult.ActivityCounts["Frontline Daily Win"]} times");
+ }
+ if (xpResult.ActivityCounts.ContainsKey("Frontline Daily Lose 2nd"))
+ {
+ ImGui.BulletText($"Take 2nd Place: {xpResult.ActivityCounts["Frontline Daily Lose 2nd"]} times");
+ }
+ if (xpResult.ActivityCounts.ContainsKey("Frontline Daily Lose 3rd"))
+ {
+ ImGui.BulletText($"Take 3rd Place: {xpResult.ActivityCounts["Frontline Daily Lose 3rd"]} times");
+ }
+
+ ImGui.Spacing();
+ ImGui.Separator();
+
+ // Rival Wings Section
+ ImGui.TextColored(new Vector4(0.6f, 0.8f, 0.6f, 1f), "Rival Wings");
+ ImGui.Spacing();
+ if (xpResult.ActivityCounts.ContainsKey("Rival Wings Win"))
+ {
+ ImGui.BulletText($"Win: {xpResult.ActivityCounts["Rival Wings Win"]} times");
+ }
+ if (xpResult.ActivityCounts.ContainsKey("Rival Wings Lose"))
+ {
+ ImGui.BulletText($"Lose: {xpResult.ActivityCounts["Rival Wings Lose"]} times");
+ }
+
+ ImGui.Spacing();
+ ImGui.Separator();
+ }
+ }
+ }
+}
diff --git a/Malmstone/packages.lock.json b/Malmstone/packages.lock.json
new file mode 100644
index 0000000..19fcea9
--- /dev/null
+++ b/Malmstone/packages.lock.json
@@ -0,0 +1,13 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net8.0-windows7.0": {
+ "DalamudPackager": {
+ "type": "Direct",
+ "requested": "[2.1.13, )",
+ "resolved": "2.1.13",
+ "contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ=="
+ }
+ }
+ }
+} \ No newline at end of file
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage