using System.Diagnostics; using System.Globalization; using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.IoC; using Dalamud.Memory; using Dalamud.Plugin; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Component.GUI; namespace PartyDamage; public class Plugin : IDalamudPlugin { [PluginService] internal static IPluginLog Log { get; private set; } [PluginService] private IAddonLifecycle AddonLifecycle { get; init; } [PluginService] private IClientState ClientState { get; init; } [PluginService] private IFramework Framework { get; init; } [PluginService] private IPartyList PartyList { get; init; } [PluginService] private IDalamudPluginInterface Interface { get; init; } private Client Client { get; } private Stopwatch Watch { get; } = Stopwatch.StartNew(); private bool _ranLastTick; public Plugin() { this.Client = new Client(); this.AddonLifecycle!.RegisterListener(AddonEvent.PostUpdate, "_PartyList", this.UpdateList); } public void Dispose() { this.AddonLifecycle.UnregisterListener(AddonEvent.PostUpdate, "_PartyList", this.UpdateList); this.Client.Dispose(); } private unsafe void UpdateList(AddonEvent type, AddonArgs args) { var ranLast = this._ranLastTick; this._ranLastTick = false; var list = (AddonPartyList*) AtkStage.Instance()->RaptureAtkUnitManager->GetAddonByName("_PartyList"); if (!this.UpdateListInner(list) && ranLast) { this.ResetMembers(list); } } private unsafe bool UpdateListInner(AddonPartyList* list) { if (this.Client.Data is not { } data) { return false; } if (!data.IsActive) { return false; } if (this.ClientState.LocalPlayer is not { } player) { return false; } var chara = (Character*) player.Address; var playerName = player.Name.TextValue; if (list->HoveredIndex >= 0 || list->TargetedIndex >= 0) { return false; } var names = new List<(string, int)>(); var members = AgentHUD.Instance()->PartyMembers; for (var i = 0; i < members.Length && i < list->PartyMembers.Length; i++) { var member = members[i]; if (member.Name == null) { continue; } // controller soft target not handled by hoveredindex above if (chara->GetSoftTargetId() == member.EntityId) { return false; } var name = MemoryHelper.ReadStringNullTerminated((nint) member.Name); names.Add((name, i)); } this._ranLastTick = true; for (var i = 0; i < names.Count; i++) { var (name, membersIdx) = names[i]; var lookupName = name == playerName ? "YOU" : name; var member = members[membersIdx]; if (!data.Combatants.TryGetValue(lookupName, out var combatant)) { continue; } this.UpdateMember(list->PartyMembers[i], member.Object, data.Encounter, combatant); } return true; } private unsafe void ResetMembers(AddonPartyList* list) { var members = AgentHUD.Instance()->PartyMembers; for (var i = 0; i < members.Length && i < list->PartyMembers.Length; i++) { var member = members[i]; if (member.Name == null) { continue; } var unit = list->PartyMembers[i]; if (list->TargetedIndex != i) { unit.TargetGlow->SetAlpha(255); unit.TargetGlow->SetScaleX(0); unit.TargetGlow->ToggleVisibility(true); } var left = (AtkTextNode*) unit.MPGaugeBar->GetTextNodeById(2); var right = (AtkTextNode*) unit.MPGaugeBar->GetTextNodeById(3); left->TextColor = new ByteColor { RGBA = 0xFFFFFFFF, }; right->TextColor = left->TextColor; var manaString = member.Object->Mana.ToString(CultureInfo.InvariantCulture); left->SetText(manaString[..^2]); right->SetText(manaString[^2..]); } } private unsafe void UpdateMember( AddonPartyList.PartyListMemberStruct member, BattleChara* chara, Encounter encounter, Combatant combatant ) { member.TargetGlow->ToggleVisibility(true); member.TargetGlow->SetAlpha(128); 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.Watch.Elapsed.Seconds % 6 < 3) { 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..]); return; } left->TextColor = new ByteColor { RGBA = 0xedffecff, }; right->TextColor = left->TextColor; 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"); } } }