2024-07-25 03:49:23 +00:00
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using Dalamud.Game.Addon.Lifecycle;
|
|
|
|
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|
|
|
|
using Dalamud.IoC;
|
|
|
|
|
using Dalamud.Memory;
|
2024-07-24 20:30:12 +00:00
|
|
|
|
using Dalamud.Plugin;
|
|
|
|
|
using Dalamud.Plugin.Services;
|
2024-07-25 03:49:23 +00:00
|
|
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
|
|
|
|
using FFXIVClientStructs.FFXIV.Client.Graphics;
|
2024-07-24 21:48:24 +00:00
|
|
|
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
2024-07-25 03:49:23 +00:00
|
|
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
2024-07-24 21:48:24 +00:00
|
|
|
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
2024-07-24 20:30:12 +00:00
|
|
|
|
|
|
|
|
|
namespace PartyDamage;
|
|
|
|
|
|
|
|
|
|
public class Plugin : IDalamudPlugin {
|
|
|
|
|
[PluginService]
|
|
|
|
|
internal static IPluginLog Log { get; private set; }
|
|
|
|
|
|
2024-07-25 03:49:23 +00:00
|
|
|
|
[PluginService]
|
|
|
|
|
private IAddonLifecycle AddonLifecycle { get; init; }
|
|
|
|
|
|
2024-07-24 21:48:24 +00:00
|
|
|
|
[PluginService]
|
|
|
|
|
private IClientState ClientState { get; init; }
|
|
|
|
|
|
|
|
|
|
[PluginService]
|
|
|
|
|
private IFramework Framework { get; init; }
|
|
|
|
|
|
2024-07-25 03:49:23 +00:00
|
|
|
|
[PluginService]
|
|
|
|
|
private IPartyList PartyList { get; init; }
|
|
|
|
|
|
|
|
|
|
[PluginService]
|
2024-07-25 06:50:32 +00:00
|
|
|
|
internal IDalamudPluginInterface Interface { get; init; }
|
2024-07-25 03:49:23 +00:00
|
|
|
|
|
2024-07-25 06:50:32 +00:00
|
|
|
|
[PluginService]
|
|
|
|
|
internal ICommandManager CommandManager { get; init; }
|
|
|
|
|
|
|
|
|
|
internal Configuration Config { get; }
|
2024-07-24 20:30:12 +00:00
|
|
|
|
private Client Client { get; }
|
2024-07-25 06:50:32 +00:00
|
|
|
|
internal PluginUi Ui { get; }
|
|
|
|
|
private Commands Commands { get; }
|
2024-07-24 20:30:12 +00:00
|
|
|
|
|
2024-07-25 06:07:07 +00:00
|
|
|
|
private Stopwatch Watch { get; } = Stopwatch.StartNew();
|
|
|
|
|
private bool _ranLastTick;
|
|
|
|
|
|
2024-07-25 06:50:32 +00:00
|
|
|
|
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
|
|
|
|
|
];
|
|
|
|
|
|
2024-07-24 20:30:12 +00:00
|
|
|
|
public Plugin() {
|
2024-07-25 06:50:32 +00:00
|
|
|
|
this.Config = this.Interface!.GetPluginConfig() as Configuration ?? new Configuration();
|
2024-07-24 20:30:12 +00:00
|
|
|
|
this.Client = new Client();
|
2024-07-25 06:50:32 +00:00
|
|
|
|
this.Ui = new PluginUi(this);
|
|
|
|
|
this.Commands = new Commands(this);
|
|
|
|
|
|
2024-07-25 03:49:23 +00:00
|
|
|
|
this.AddonLifecycle!.RegisterListener(AddonEvent.PostUpdate, "_PartyList", this.UpdateList);
|
2024-07-24 20:30:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose() {
|
2024-07-25 03:49:23 +00:00
|
|
|
|
this.AddonLifecycle.UnregisterListener(AddonEvent.PostUpdate, "_PartyList", this.UpdateList);
|
2024-07-25 06:50:32 +00:00
|
|
|
|
this.Commands.Dispose();
|
|
|
|
|
this.Ui.Dispose();
|
2024-07-24 20:30:12 +00:00
|
|
|
|
this.Client.Dispose();
|
|
|
|
|
}
|
2024-07-24 21:48:24 +00:00
|
|
|
|
|
2024-07-25 06:50:32 +00:00
|
|
|
|
internal void SaveConfig() {
|
|
|
|
|
this.Interface.SavePluginConfig(this.Config);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 06:07:07 +00:00
|
|
|
|
private unsafe void UpdateList(AddonEvent type, AddonArgs args) {
|
|
|
|
|
var ranLast = this._ranLastTick;
|
|
|
|
|
this._ranLastTick = false;
|
2024-07-25 03:49:23 +00:00
|
|
|
|
var list = (AddonPartyList*) AtkStage.Instance()->RaptureAtkUnitManager->GetAddonByName("_PartyList");
|
2024-07-25 06:07:07 +00:00
|
|
|
|
if (!this.UpdateListInner(list) && ranLast) {
|
|
|
|
|
this.ResetMembers(list);
|
2024-07-25 03:49:23 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 06:07:07 +00:00
|
|
|
|
private unsafe bool UpdateListInner(AddonPartyList* list) {
|
2024-07-24 21:48:24 +00:00
|
|
|
|
if (this.Client.Data is not { } data) {
|
2024-07-25 06:07:07 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!data.IsActive) {
|
|
|
|
|
return false;
|
2024-07-24 21:48:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.ClientState.LocalPlayer is not { } player) {
|
2024-07-25 06:07:07 +00:00
|
|
|
|
return false;
|
2024-07-24 21:48:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 03:49:23 +00:00
|
|
|
|
var chara = (Character*) player.Address;
|
2024-07-25 03:56:10 +00:00
|
|
|
|
var playerName = player.Name.TextValue;
|
2024-07-25 03:49:23 +00:00
|
|
|
|
|
|
|
|
|
if (list->HoveredIndex >= 0 || list->TargetedIndex >= 0) {
|
2024-07-25 06:07:07 +00:00
|
|
|
|
return false;
|
2024-07-25 03:49:23 +00:00
|
|
|
|
}
|
2024-07-24 21:48:24 +00:00
|
|
|
|
|
2024-07-25 06:07:07 +00:00
|
|
|
|
var names = new List<(string, int)>();
|
2024-07-25 03:56:10 +00:00
|
|
|
|
var members = AgentHUD.Instance()->PartyMembers;
|
|
|
|
|
for (var i = 0; i < members.Length && i < list->PartyMembers.Length; i++) {
|
|
|
|
|
var member = members[i];
|
2024-07-25 03:49:23 +00:00
|
|
|
|
if (member.Name == null) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// controller soft target not handled by hoveredindex above
|
|
|
|
|
if (chara->GetSoftTargetId() == member.EntityId) {
|
2024-07-25 06:07:07 +00:00
|
|
|
|
return false;
|
2024-07-25 03:49:23 +00:00
|
|
|
|
}
|
2024-07-24 21:48:24 +00:00
|
|
|
|
|
2024-07-25 03:49:23 +00:00
|
|
|
|
var name = MemoryHelper.ReadStringNullTerminated((nint) member.Name);
|
2024-07-25 06:07:07 +00:00
|
|
|
|
names.Add((name, i));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._ranLastTick = true;
|
2024-07-24 21:48:24 +00:00
|
|
|
|
|
2024-07-25 06:07:07 +00:00
|
|
|
|
for (var i = 0; i < names.Count; i++) {
|
|
|
|
|
var (name, membersIdx) = names[i];
|
2024-07-25 03:56:10 +00:00
|
|
|
|
var lookupName = name == playerName
|
|
|
|
|
? "YOU"
|
|
|
|
|
: name;
|
2024-07-25 06:07:07 +00:00
|
|
|
|
var member = members[membersIdx];
|
2024-07-24 22:04:52 +00:00
|
|
|
|
|
2024-07-25 03:56:10 +00:00
|
|
|
|
if (!data.Combatants.TryGetValue(lookupName, out var combatant)) {
|
2024-07-24 21:48:24 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 03:56:10 +00:00
|
|
|
|
this.UpdateMember(list->PartyMembers[i], member.Object, data.Encounter, combatant);
|
2024-07-24 21:48:24 +00:00
|
|
|
|
}
|
2024-07-25 03:56:10 +00:00
|
|
|
|
|
2024-07-25 06:07:07 +00:00
|
|
|
|
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..]);
|
2024-07-25 06:50:32 +00:00
|
|
|
|
|
|
|
|
|
unit.Name->SetText(member.Object->Name);
|
|
|
|
|
unit.Name->TextColor = new ByteColor {
|
|
|
|
|
RGBA = 0xFFFFFFFF,
|
|
|
|
|
};
|
2024-07-25 06:07:07 +00:00
|
|
|
|
}
|
2024-07-24 21:48:24 +00:00
|
|
|
|
}
|
2024-07-24 22:04:52 +00:00
|
|
|
|
|
2024-07-25 03:56:10 +00:00
|
|
|
|
private unsafe void UpdateMember(
|
|
|
|
|
AddonPartyList.PartyListMemberStruct member,
|
|
|
|
|
BattleChara* chara,
|
|
|
|
|
Encounter encounter,
|
|
|
|
|
Combatant combatant
|
|
|
|
|
) {
|
2024-07-25 06:50:32 +00:00
|
|
|
|
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
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 03:49:23 +00:00
|
|
|
|
left->TextColor = new ByteColor {
|
2024-07-25 06:50:32 +00:00
|
|
|
|
RGBA = this.Config.DpsColour,
|
2024-07-25 03:49:23 +00:00
|
|
|
|
};
|
|
|
|
|
right->TextColor = left->TextColor;
|
|
|
|
|
|
2024-07-25 06:50:32 +00:00
|
|
|
|
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",
|
|
|
|
|
};
|
2024-07-25 03:56:10 +00:00
|
|
|
|
|
2024-07-25 06:50:32 +00:00
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
if (this.Watch.Elapsed.Seconds >= this.Config.AlternateSeconds * 2) {
|
|
|
|
|
this.Watch.Restart();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-25 03:49:23 +00:00
|
|
|
|
|
2024-07-25 06:50:32 +00:00
|
|
|
|
member.Name->SetText(dpsText);
|
|
|
|
|
member.Name->TextColor = new ByteColor {
|
|
|
|
|
RGBA = this.Config.DpsColour,
|
|
|
|
|
};
|
2024-07-25 03:49:23 +00:00
|
|
|
|
}
|
2024-07-24 22:04:52 +00:00
|
|
|
|
}
|
2024-07-24 20:30:12 +00:00
|
|
|
|
}
|