diff --git a/Commands.cs b/Commands.cs new file mode 100644 index 0000000..e4e07a0 --- /dev/null +++ b/Commands.cs @@ -0,0 +1,21 @@ +using Dalamud.Game.Command; + +namespace PartyDamage; + +public class Commands : IDisposable { + private Plugin Plugin { get; } + + public Commands(Plugin plugin) { + this.Plugin = plugin; + + this.Plugin.CommandManager.AddHandler("/partydamage", new CommandInfo(this.OnCommand)); + } + + public void Dispose() { + this.Plugin.CommandManager.RemoveHandler("/partydamage"); + } + + private void OnCommand(string command, string args) { + this.Plugin.Ui.Visible ^= true; + } +} \ No newline at end of file diff --git a/Configuration.cs b/Configuration.cs new file mode 100644 index 0000000..a2fb6b2 --- /dev/null +++ b/Configuration.cs @@ -0,0 +1,20 @@ +using Dalamud.Configuration; + +namespace PartyDamage; + +public class Configuration : IPluginConfiguration { + public int Version { get; set; } = 1; + + public bool UseDpsBar = true; + public float BarAlpha = 0.5f; + public MeterMode Mode = MeterMode.Mana; + public bool Alternate = true; + public bool ManaModeAlternateOnlyManaUsers = true; + public float AlternateSeconds = 3.0f; + public uint DpsColour = 0xEDFFEC; +} + +public enum MeterMode { + Name, + Mana, +} \ No newline at end of file diff --git a/ImGuiHelper.cs b/ImGuiHelper.cs new file mode 100644 index 0000000..b911473 --- /dev/null +++ b/ImGuiHelper.cs @@ -0,0 +1,18 @@ +using ImGuiNET; + +namespace PartyDamage; + +public static class ImGuiHelper { + public static OnDispose? DisabledIf(bool disabled) { + if (disabled) { + ImGui.BeginDisabled(); + return new OnDispose(ImGui.EndDisabled); + } + + return null; + } + + public static OnDispose? DisabledUnless(bool unless) { + return DisabledIf(!unless); + } +} \ No newline at end of file diff --git a/OnDispose.cs b/OnDispose.cs new file mode 100644 index 0000000..92aed13 --- /dev/null +++ b/OnDispose.cs @@ -0,0 +1,19 @@ +namespace PartyDamage; + +public class OnDispose : IDisposable { + private readonly Action _action; + private bool _disposed; + + public OnDispose(Action action) { + this._action = action; + } + + public void Dispose() { + if (this._disposed) { + return; + } + + this._disposed = true; + this._action(); + } +} \ No newline at end of file diff --git a/Plugin.cs b/Plugin.cs index 3e5b4c1..8c56e90 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -31,23 +31,56 @@ public class Plugin : IDalamudPlugin { private IPartyList PartyList { get; init; } [PluginService] - private IDalamudPluginInterface Interface { get; init; } + internal IDalamudPluginInterface Interface { get; init; } + [PluginService] + internal ICommandManager CommandManager { get; init; } + + internal Configuration Config { get; } private Client Client { get; } + internal PluginUi Ui { get; } + private Commands Commands { get; } private Stopwatch Watch { get; } = Stopwatch.StartNew(); private bool _ranLastTick; + private readonly byte[] ManaUsers = [ + 6, // cnj + 7, // thm + 19, // pld + 24, // whm + 25, // blm + 26, // acn + 27, // smn + 28, // sch + 32, // drk + 33, // ast + 35, // rdm + 36, // blu + 40, // sge + 42, // pct + ]; + public Plugin() { + this.Config = this.Interface!.GetPluginConfig() as Configuration ?? new Configuration(); this.Client = new Client(); + this.Ui = new PluginUi(this); + this.Commands = new Commands(this); + this.AddonLifecycle!.RegisterListener(AddonEvent.PostUpdate, "_PartyList", this.UpdateList); } public void Dispose() { this.AddonLifecycle.UnregisterListener(AddonEvent.PostUpdate, "_PartyList", this.UpdateList); + this.Commands.Dispose(); + this.Ui.Dispose(); this.Client.Dispose(); } + internal void SaveConfig() { + this.Interface.SavePluginConfig(this.Config); + } + private unsafe void UpdateList(AddonEvent type, AddonArgs args) { var ranLast = this._ranLastTick; this._ranLastTick = false; @@ -140,6 +173,11 @@ public class Plugin : IDalamudPlugin { var manaString = member.Object->Mana.ToString(CultureInfo.InvariantCulture); left->SetText(manaString[..^2]); right->SetText(manaString[^2..]); + + unit.Name->SetText(member.Object->Name); + unit.Name->TextColor = new ByteColor { + RGBA = 0xFFFFFFFF, + }; } } @@ -149,46 +187,103 @@ public class Plugin : IDalamudPlugin { Encounter encounter, Combatant combatant ) { - member.TargetGlow->ToggleVisibility(true); - member.TargetGlow->SetAlpha(128); - member.TargetGlow->SetScaleX( - encounter.EncDps == 0 - ? 0 - : combatant.EncDps / encounter.EncDps - ); + if (this.Config.UseDpsBar) { + member.TargetGlow->ToggleVisibility(true); + member.TargetGlow->SetAlpha((byte) Math.Round(this.Config.BarAlpha * 255)); + member.TargetGlow->SetScaleX( + encounter.EncDps == 0 + ? 0 + : combatant.EncDps / encounter.EncDps + ); + } - var left = (AtkTextNode*) member.MPGaugeBar->GetTextNodeById(2); - var right = (AtkTextNode*) member.MPGaugeBar->GetTextNodeById(3); + if (this.Config.Mode == MeterMode.Mana) { + var left = (AtkTextNode*) member.MPGaugeBar->GetTextNodeById(2); + var right = (AtkTextNode*) member.MPGaugeBar->GetTextNodeById(3); + + if (this.Config.Alternate) { + var isCaster = Array.IndexOf(this.ManaUsers, chara->ClassJob) != -1; + if (!this.Config.ManaModeAlternateOnlyManaUsers || isCaster) { + var elapsedSeconds = this.Watch.Elapsed.Seconds; + if (elapsedSeconds >= this.Config.AlternateSeconds * 2) { + this.Watch.Restart(); + } else if (elapsedSeconds >= this.Config.AlternateSeconds) { + left->TextColor = new ByteColor { + RGBA = 0xFFFFFFFF, + }; + right->TextColor = left->TextColor; + + var manaString = chara->Mana.ToString(CultureInfo.InvariantCulture); + left->SetText(manaString[..^2]); + right->SetText(manaString[^2..]); + + if (this.Watch.Elapsed.Seconds >= this.Config.AlternateSeconds * 2) { + this.Watch.Restart(); + } + + return; + } + } + } - if (this.Watch.Elapsed.Seconds % 6 < 3) { left->TextColor = new ByteColor { - RGBA = 0xFFFFFFFF, + RGBA = this.Config.DpsColour, }; right->TextColor = left->TextColor; - var manaString = chara->Mana.ToString(CultureInfo.InvariantCulture); - left->SetText(manaString[..^2]); - right->SetText(manaString[^2..]); + if (combatant.EncDps == 0 || float.IsInfinity(combatant.EncDps) || float.IsNaN(combatant.EncDps)) { + left->SetText("0."); + right->SetText("00"); + } else if (combatant.EncDps < 1_000) { + var dps = Math.Round(combatant.EncDps * 100).ToString(CultureInfo.InvariantCulture); + left->SetText($"{dps[..^2]}."); + right->SetText(dps[^2..]); + } else if (combatant.EncDps < 1_000_000) { + var dps = Math.Round(combatant.EncDps / 100).ToString(CultureInfo.InvariantCulture); + left->SetText($"{dps[..^1]}."); + right->SetText($"{dps[^1..]}K"); + } else if (combatant.EncDps < 1_000_000_000) { + var dps = Math.Round(combatant.EncDps / 100_000).ToString(CultureInfo.InvariantCulture); + left->SetText($"{dps[..^1]}."); + right->SetText($"{dps[^1..]}M"); + } else { + var dps = Math.Round(combatant.EncDps / 100_000_000).ToString(CultureInfo.InvariantCulture); + left->SetText($"{dps[..^1]}."); + right->SetText($"{dps[^1..]}B"); + } + } else if (this.Config.Mode == MeterMode.Name) { + var dpsText = combatant.EncDps switch { + float.NaN => "0", + float.PositiveInfinity => "0", + float.NegativeInfinity => "0", + < 1_000 => $"{combatant.EncDps:N2}", + < 1_000_000 => $"{combatant.EncDps / 1_000:N2}K", + < 1_000_000_000 => $"{combatant.EncDps / 1_000_000:N2}M", + _ => $"{combatant.EncDps / 1_000_000_000:N2}B", + }; - return; - } + if (this.Config.Alternate) { + var elapsedSeconds = this.Watch.Elapsed.Seconds; + if (elapsedSeconds >= this.Config.AlternateSeconds * 2) { + this.Watch.Restart(); + } else if (elapsedSeconds >= this.Config.AlternateSeconds) { + member.Name->TextColor = new ByteColor { + RGBA = 0xFFFFFFFF, + }; + member.Name->SetText(chara->Name); - left->TextColor = new ByteColor { - RGBA = 0xedffecff, - }; - right->TextColor = left->TextColor; + if (this.Watch.Elapsed.Seconds >= this.Config.AlternateSeconds * 2) { + this.Watch.Restart(); + } - if (combatant.EncDps == 0 || float.IsInfinity(combatant.EncDps) || float.IsNaN(combatant.EncDps)) { - left->SetText("0."); - right->SetText("00"); - } else if (combatant.EncDps < 1_000) { - var dps = Math.Round(combatant.EncDps * 100).ToString(CultureInfo.InvariantCulture); - left->SetText($"{dps[..^2]}."); - right->SetText(dps[^2..]); - } else if (combatant.EncDps < 1_000_000) { - var dps = Math.Round(combatant.EncDps / 100).ToString(CultureInfo.InvariantCulture); - left->SetText($"{dps[..^1]}."); - right->SetText($"{dps[^1..]}K"); + return; + } + } + + member.Name->SetText(dpsText); + member.Name->TextColor = new ByteColor { + RGBA = this.Config.DpsColour, + }; } } } diff --git a/PluginUi.cs b/PluginUi.cs new file mode 100644 index 0000000..b763479 --- /dev/null +++ b/PluginUi.cs @@ -0,0 +1,76 @@ +using System.Numerics; +using ImGuiNET; + +namespace PartyDamage; + +public class PluginUi : IDisposable { + private Plugin Plugin { get; } + internal bool Visible; + + public PluginUi(Plugin plugin) { + this.Plugin = plugin; + + this.Plugin.Interface.UiBuilder.Draw += this.Draw; + } + + public void Dispose() { + this.Plugin.Interface.UiBuilder.Draw -= this.Draw; + } + + private void Draw() { + var anyChanged = false; + + ImGui.TextUnformatted("Meter mode"); + if (ImGui.BeginCombo("##mode", Enum.GetName(this.Plugin.Config.Mode))) { + using var endCombo = new OnDispose(ImGui.EndCombo); + + foreach (var mode in Enum.GetValues()) { + if (ImGui.Selectable(Enum.GetName(mode), mode == this.Plugin.Config.Mode)) { + anyChanged = true; + this.Plugin.Config.Mode = mode; + } + } + } + + anyChanged |= ImGui.Checkbox("Use DPS bars behind party list", ref this.Plugin.Config.UseDpsBar); + var barAlpha = this.Plugin.Config.BarAlpha * 100; + if (ImGui.SliderFloat("Bar opacity", ref barAlpha, 0, 100, "%.2f%%")) { + this.Plugin.Config.BarAlpha = Math.Clamp(barAlpha / 100, 0, 1); + } + + anyChanged |= ImGui.Checkbox("Alternate between values", ref this.Plugin.Config.Alternate); + using (ImGuiHelper.DisabledUnless(this.Plugin.Config.Alternate)) { + anyChanged |= ImGui.SliderFloat("Seconds before alternating", ref this.Plugin.Config.AlternateSeconds, 0.5f, 60f); + } + + using (ImGuiHelper.DisabledUnless(this.Plugin.Config.Alternate && this.Plugin.Config.Mode == MeterMode.Name)) { + anyChanged |= ImGui.Checkbox("Only alternate on jobs that use mana", ref this.Plugin.Config.ManaModeAlternateOnlyManaUsers); + } + + var textColour = ConvertRgba(this.Plugin.Config.DpsColour); + if (ImGui.ColorPicker3("DPS text colour", ref textColour)) { + anyChanged = true; + this.Plugin.Config.DpsColour = ConvertRgba(textColour); + } + } + + private static Vector3 ConvertRgba(uint colour) { + var red = colour & 0xFF; + var green = (colour >> 8) & 0xFF; + var blue = (colour >> 16) & 0xFF; + // var alpha = colour >> 24; + + return new Vector3(red / 255f, green / 255f, blue / 255f); + } + + private static uint ConvertRgba(Vector3 parts) { + var red = (uint) Math.Round(parts.X * 255); + var green = (uint) Math.Round(parts.Y * 255); + var blue = (uint) Math.Round(parts.Z * 255); + + return (red << 24) + | (green << 16) + | (blue << 8) + | 0xFF; + } +} \ No newline at end of file