PartyDamage/Plugin.cs
2024-07-25 04:32:25 -04:00

301 lines
10 KiB
C#

using System.Diagnostics;
using System.Globalization;
using System.Text;
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]
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 bool _showDps = true;
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;
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;
if (this.Watch.Elapsed.TotalMilliseconds > this.Config.AlternateSeconds * 1_000) {
this.Watch.Restart();
this._showDps ^= 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->AddRed = 0;
unit.TargetGlow->AddGreen = 0;
unit.TargetGlow->AddBlue = 0;
unit.TargetGlow->MultiplyRed = 100;
unit.TargetGlow->MultiplyGreen = 100;
unit.TargetGlow->MultiplyBlue = 100;
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..]);
// fixme: need to figure out a way to get the game to repop
var defaultName = new StringBuilder("\ue06a");
foreach (var digit in member.Object->Level.ToString(CultureInfo.InvariantCulture)) {
var offset = digit - '0';
defaultName.Append((char) ('\ue060' + offset));
}
defaultName.Append(' ');
defaultName.Append(member.Object->NameString);
unit.Name->SetText(defaultName.ToString());
unit.Name->TextColor = new ByteColor {
RGBA = 0xFFFFFFFF,
};
}
}
private unsafe void UpdateMember(
AddonPartyList.PartyListMemberStruct member,
BattleChara* chara,
Encounter encounter,
Combatant combatant
) {
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
);
member.TargetGlow->AddRed = (byte) Math.Clamp(this.Config.BarAddRed, 0, 255);
member.TargetGlow->AddGreen = (byte) Math.Clamp(this.Config.BarAddGreen, 0, 255);
member.TargetGlow->AddBlue = (byte) Math.Clamp(this.Config.BarAddBlue, 0, 255);
member.TargetGlow->MultiplyRed = (byte) Math.Clamp(this.Config.BarMulRed, 0, 100);
member.TargetGlow->MultiplyGreen = (byte) Math.Clamp(this.Config.BarMulGreen, 0, 100);
member.TargetGlow->MultiplyBlue = (byte) Math.Clamp(this.Config.BarMulBlue, 0, 100);
}
if (this.Config.Mode == MeterMode.Mana) {
var left = (AtkTextNode*) member.MPGaugeBar->GetTextNodeById(2);
var right = (AtkTextNode*) member.MPGaugeBar->GetTextNodeById(3);
if (this.Config.Alternate && !this._showDps) {
var isCaster = Array.IndexOf(this._manaUsers, chara->ClassJob) != -1;
if (!this.Config.ManaModeAlternateOnlyManaUsers || isCaster) {
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 = this.Config.DpsColour,
};
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");
} 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",
};
if (this.Config.Alternate && !this._showDps) {
member.Name->TextColor = new ByteColor {
RGBA = 0xFFFFFFFF,
};
member.Name->SetText(chara->Name);
return;
}
member.Name->SetText(dpsText);
member.Name->TextColor = new ByteColor {
RGBA = this.Config.DpsColour,
};
}
}
}