diff options
| author | Pinapelz <yukais@pinapelz.com> | 2024-08-27 22:21:18 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2024-08-27 22:21:18 -0700 |
| commit | 72f85fd952c8e53230c968ef9a433644dae90254 (patch) | |
| tree | a93b73e9d045243cdc4ee467376ef8573f297d2c /Malmstone | |
| parent | b8c43fbe717d794284c6c4578c9c00ae8e26d711 (diff) | |
Initial Version 1.0.0.0
Diffstat (limited to 'Malmstone')
| -rw-r--r-- | Malmstone/Configuration.cs | 19 | ||||
| -rw-r--r-- | Malmstone/Dalamud.Plugin.Bootstrap.targets | 11 | ||||
| -rw-r--r-- | Malmstone/Malmstone.csproj | 12 | ||||
| -rw-r--r-- | Malmstone/Malmstone.json | 13 | ||||
| -rw-r--r-- | Malmstone/Plugin.cs | 63 | ||||
| -rw-r--r-- | Malmstone/Services/PVPService.cs | 32 | ||||
| -rw-r--r-- | Malmstone/Utils/MalmstoneXPCalculator.cs | 107 | ||||
| -rw-r--r-- | Malmstone/Windows/ConfigWindow.cs | 44 | ||||
| -rw-r--r-- | Malmstone/Windows/MainWindow.cs | 136 | ||||
| -rw-r--r-- | Malmstone/packages.lock.json | 13 |
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 |
