PartyDamage/Plugin.cs

490 lines
17 KiB
C#
Raw Normal View History

2024-07-25 03:49:23 +00:00
using System.Diagnostics;
using System.Globalization;
using System.Text;
2024-07-25 03:49:23 +00:00
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
2024-07-25 22:21:46 +00:00
using Dalamud.Game.Gui.ContextMenu;
using Dalamud.Game.Text;
2024-07-25 03:49:23 +00:00
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;
2024-07-25 22:21:46 +00:00
using FFXIVClientStructs.FFXIV.Client.Game.Group;
2024-07-25 03:49:23 +00:00
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-27 23:48:29 +00:00
using Lumina.Excel.GeneratedSheets;
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; }
2024-07-25 22:21:46 +00:00
[PluginService]
internal IContextMenu ContextMenu { get; init; }
2024-07-27 23:48:29 +00:00
[PluginService]
internal IDataManager DataManager { get; init; }
2024-07-25 06:50:32 +00:00
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 22:21:46 +00:00
private Stopwatch AlternateWatch { get; } = Stopwatch.StartNew();
private Stopwatch DelayWatch { get; } = new();
2024-07-25 06:07:07 +00:00
private bool _ranLastTick;
2024-07-25 08:32:25 +00:00
private bool _showDps = true;
2024-07-25 22:21:46 +00:00
private bool _reset;
private bool _wasActive;
private bool _manuallyReset = true;
2024-07-25 06:07:07 +00:00
2024-07-25 08:32:25 +00:00
private readonly byte[] _manaUsers = [
2024-07-25 06:50:32 +00:00
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-25 22:21:46 +00:00
this.ContextMenu!.OnMenuOpened += this.MenuOpened;
2024-07-24 20:30:12 +00:00
}
public void Dispose() {
2024-07-25 22:21:46 +00:00
this.ContextMenu.OnMenuOpened -= this.MenuOpened;
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 22:21:46 +00:00
private unsafe void MenuOpened(IMenuOpenedArgs args) {
var add = args.AddonName == "_PartyList";
if (!add) {
var ctx = AgentContext.Instance();
if (args.AgentPtr != (nint) ctx) {
return;
}
var targetId = ctx->TargetObjectId.ObjectId;
if (targetId == 0xE000_0000) {
return;
}
if (this.ClientState.LocalPlayer is { } player && player.GameObjectId == targetId) {
add = true;
} else {
var group = GroupManager.Instance()->GetGroup();
if (group != null && group->GetPartyMemberByEntityId(targetId) != null) {
add = true;
}
}
}
if (!add) {
return;
}
args.AddMenuItem(new MenuItem {
Name = "Clear parse results",
Prefix = SeIconChar.BoxedLetterP,
PrefixColor = 37,
IsEnabled = this.Client.Data?.IsActive == false,
OnClicked = _ => {
this._reset = true;
this._manuallyReset = true;
},
});
}
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 22:21:46 +00:00
if (list == null) {
return;
}
var shouldUpdate = false;
var wasActive = this._wasActive;
var becameInactive = false;
if (this.Client.Data is { } data) {
if (wasActive && !data.IsActive) {
becameInactive = true;
}
this._wasActive = data.IsActive;
}
if (this.Client.Data is { IsActive: true }) {
shouldUpdate = true;
this._manuallyReset = false;
} else if (becameInactive) {
if (this.Config.ClearResultsOnInactive) {
this.DelayWatch.Restart();
}
2024-07-27 20:54:23 +00:00
if (
this.Config.UseEvaluatorNpc
2024-07-27 23:48:29 +00:00
&& this.ClientState.LocalPlayer is { } player
&& this.Client.Data != null
&& Evaluator.Evaluators.FirstOrDefault(e => e.Id == this.Config.EvaluatorId) is { } evaluator
) {
2024-07-27 23:48:29 +00:00
var combatants = this.Client.Data.Combatants.Values
.Where(combatant => {
var job = this.DataManager.GetExcelSheet<ClassJob>()!
.FirstOrDefault(j => j.Abbreviation.RawString.Equals(combatant.JobAbbr, StringComparison.InvariantCultureIgnoreCase));
return job?.ClassJobCategory.Row == player.ClassJob.GameData?.ClassJobCategory.Row;
})
.ToList();
combatants.Sort((a, b) => a.EncDps.CompareTo(b.EncDps));
var rank = (float) (combatants.FindIndex(combatant => combatant.Name == "YOU") + 1) / combatants.Count;
var evaluation = rank switch {
>= .85f => Evaluation.Best,
>= .65f => Evaluation.Good,
>= .45f => Evaluation.Fair,
>= .25f => Evaluation.Poor,
_ => Evaluation.Awful,
2024-07-27 20:54:23 +00:00
};
var msg = evaluator.GetLineFor(evaluation);
if (msg != null) {
var bytes = msg.EncodeWithNullTerminator();
UIModule.Instance()->ShowBattleTalkImage(
Encoding.UTF8.GetBytes(evaluator.Name),
bytes,
5f,
evaluator.BattleTalkImage,
0
);
}
2024-07-27 20:54:23 +00:00
}
2024-07-25 22:21:46 +00:00
}
if (this.Config.ClearResultsOnInactive && this.DelayWatch.IsRunning && this.DelayWatch.Elapsed >= TimeSpan.FromSeconds(this.Config.ClearDelaySeconds)) {
this.DelayWatch.Reset();
this._reset = true;
}
if (!this._reset && !this._manuallyReset) {
// only do these checks if we shouldn't reset and if we're waiting for active data
// keep running if we're in the delay period
if (this.DelayWatch.IsRunning) {
shouldUpdate = true;
}
// keep running if the user wants to manually clear results
if (!this.Config.ClearResultsOnInactive) {
shouldUpdate = true;
}
}
if (this._reset) {
this.DelayWatch.Reset();
this._reset = false;
this._manuallyReset = true;
this.ResetMembers(list);
}
if (shouldUpdate && this.UpdateListInner(list) && ranLast) {
2024-07-25 06:07:07 +00:00
this.ResetMembers(list);
2024-07-25 03:49:23 +00:00
}
}
2024-07-25 22:21:46 +00:00
/// <summary>
/// Update the party list.
/// </summary>
/// <returns>true if the list should be reset immediately</returns>
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;
}
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 22:21:46 +00:00
return true;
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 22:21:46 +00:00
return true;
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 22:21:46 +00:00
if (this.AlternateWatch.Elapsed.TotalMilliseconds > this.Config.AlternateSeconds * 1_000) {
this.AlternateWatch.Restart();
2024-07-25 08:32:25 +00:00
this._showDps ^= true;
}
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-27 20:54:23 +00:00
this.UpdateMember(list, 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 22:21:46 +00:00
return false;
2024-07-25 06:07:07 +00:00
}
2024-07-27 20:54:23 +00:00
private unsafe void ResetMember(
AddonPartyList* list,
BattleChara* chara,
int listIndex,
bool includeTargeted
) {
var unit = list->PartyMembers[listIndex];
if (unit.TargetGlow != null && (includeTargeted || list->TargetedIndex != listIndex)) {
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);
}
2024-07-25 06:07:07 +00:00
2024-07-27 20:54:23 +00:00
var left = (AtkTextNode*) unit.MPGaugeBar->GetTextNodeById(2);
var right = (AtkTextNode*) unit.MPGaugeBar->GetTextNodeById(3);
2024-07-25 06:07:07 +00:00
2024-07-27 20:54:23 +00:00
var hasLeft = left != null && left->IsVisible();
var hasRight = right != null && right->IsVisible();
var hasChara = chara != null;
2024-07-26 00:02:46 +00:00
2024-07-27 20:54:23 +00:00
if (!hasLeft) {
return;
}
2024-07-25 06:07:07 +00:00
2024-07-27 20:54:23 +00:00
left->TextColor = new ByteColor {
RGBA = 0xFFFFFFFF,
};
var manaString = hasChara
? chara->Mana.ToString(CultureInfo.InvariantCulture)
: "???";
if (hasRight) {
if (manaString.Length <= 1) {
left->SetText("");
right->SetText(manaString);
} else {
left->SetText(manaString[..^2]);
right->SetText(manaString[^2..]);
}
2024-07-27 20:54:23 +00:00
} else {
left->SetText(manaString);
}
2024-07-27 20:54:23 +00:00
left->AddRed = 0;
left->AddGreen = 0;
left->AddBlue = 0;
left->MultiplyRed = 100;
left->MultiplyGreen = 100;
left->MultiplyBlue = 100;
if (hasRight) {
right->TextColor = left->TextColor;
right->AddRed = left->AddRed;
right->AddGreen = left->AddGreen;
right->AddBlue = left->AddBlue;
right->MultiplyRed = left->MultiplyRed;
right->MultiplyGreen = left->MultiplyGreen;
right->MultiplyBlue = left->MultiplyBlue;
}
2024-07-25 06:07:07 +00:00
2024-07-27 20:54:23 +00:00
// 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));
// }
2024-07-25 06:50:32 +00:00
2024-07-27 20:54:23 +00:00
// defaultName.Append(' ');
// defaultName.Append(member.Object->NameString);
2024-07-25 20:01:02 +00:00
2024-07-27 20:54:23 +00:00
// unit.Name->SetText(defaultName.ToString());
// unit.Name->TextColor = new ByteColor {
// RGBA = 0xFFFFFFFF,
// };
// unit.Name->AddRed = 0;
// unit.Name->AddGreen = 0;
// unit.Name->AddBlue = 0;
// unit.Name->MultiplyRed = 100;
// unit.Name->MultiplyGreen = 100;
// unit.Name->MultiplyBlue = 100;
}
2024-07-25 08:32:25 +00:00
2024-07-27 20:54:23 +00:00
private unsafe void ResetMembers(AddonPartyList* list) {
var members = AgentHUD.Instance()->PartyMembers;
for (var i = 0; i < members.Length && i < list->PartyMembers.Length; i++) {
this.ResetMember(list, members[i].Object, i, false);
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(
2024-07-27 20:54:23 +00:00
AddonPartyList* list,
int listIndex,
2024-07-25 03:56:10 +00:00
BattleChara* chara,
Encounter encounter,
Combatant combatant
) {
2024-07-27 20:54:23 +00:00
var member = list->PartyMembers[listIndex];
2024-07-26 00:02:46 +00:00
if (this.Config.UseDpsBar && member.TargetGlow != null) {
2024-07-25 06:50:32 +00:00
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
);
2024-07-25 08:32:25 +00:00
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);
2024-07-25 06:50:32 +00:00
}
if (this.Config.Mode == MeterMode.Mana) {
var left = (AtkTextNode*) member.MPGaugeBar->GetTextNodeById(2);
var right = (AtkTextNode*) member.MPGaugeBar->GetTextNodeById(3);
2024-07-27 05:53:01 +00:00
var hasLeft = left != null && left->IsVisible();
var hasRight = right != null && right->IsVisible();
2024-07-26 00:02:46 +00:00
var hasChara = chara != null;
2024-07-25 06:50:32 +00:00
if (!hasLeft) {
return;
}
2024-07-25 08:32:25 +00:00
if (this.Config.Alternate && !this._showDps) {
var isCaster = hasChara && Array.IndexOf(this._manaUsers, chara->ClassJob) != -1;
2024-07-25 06:50:32 +00:00
if (!this.Config.ManaModeAlternateOnlyManaUsers || isCaster) {
2024-07-27 20:54:23 +00:00
this.ResetMember(list, chara, listIndex, true);
2024-07-25 08:32:25 +00:00
return;
2024-07-25 06:50:32 +00:00
}
}
2024-07-25 03:49:23 +00:00
left->TextColor = new ByteColor {
2024-07-25 20:01:02 +00:00
RGBA = this.Config.TextColour,
2024-07-25 03:49:23 +00:00
};
2024-07-25 20:01:02 +00:00
left->AddRed = (byte) Math.Clamp(this.Config.TextAddRed, 0, 255);
left->AddGreen = (byte) Math.Clamp(this.Config.TextAddGreen, 0, 255);
left->AddBlue = (byte) Math.Clamp(this.Config.TextAddBlue, 0, 255);
left->MultiplyRed = (byte) Math.Clamp(this.Config.TextMulRed, 0, 100);
left->MultiplyGreen = (byte) Math.Clamp(this.Config.TextMulGreen, 0, 100);
left->MultiplyBlue = (byte) Math.Clamp(this.Config.TextMulBlue, 0, 100);
2024-07-26 00:02:46 +00:00
if (hasRight) {
right->TextColor = left->TextColor;
right->AddRed = left->AddRed;
right->AddGreen = left->AddGreen;
right->AddBlue = left->AddBlue;
right->MultiplyRed = left->MultiplyRed;
right->MultiplyGreen = left->MultiplyGreen;
right->MultiplyBlue = left->MultiplyBlue;
}
string leftText, rightText;
2024-07-25 20:01:02 +00:00
2024-07-25 06:50:32 +00:00
if (combatant.EncDps == 0 || float.IsInfinity(combatant.EncDps) || float.IsNaN(combatant.EncDps)) {
2024-07-26 00:02:46 +00:00
leftText = "0.";
rightText = "00";
2024-07-25 06:50:32 +00:00
} else if (combatant.EncDps < 1_000) {
var dps = Math.Round(combatant.EncDps * 100).ToString(CultureInfo.InvariantCulture);
2024-07-26 00:02:46 +00:00
leftText = $"{dps[..^2]}.";
rightText = dps[^2..];
2024-07-25 06:50:32 +00:00
} else if (combatant.EncDps < 1_000_000) {
var dps = Math.Round(combatant.EncDps / 100).ToString(CultureInfo.InvariantCulture);
2024-07-26 00:02:46 +00:00
leftText = $"{dps[..^1]}.";
rightText = $"{dps[^1..]}K";
2024-07-25 06:50:32 +00:00
} else if (combatant.EncDps < 1_000_000_000) {
var dps = Math.Round(combatant.EncDps / 100_000).ToString(CultureInfo.InvariantCulture);
2024-07-26 00:02:46 +00:00
leftText = $"{dps[..^1]}.";
rightText = $"{dps[^1..]}M";
2024-07-25 06:50:32 +00:00
} else {
var dps = Math.Round(combatant.EncDps / 100_000_000).ToString(CultureInfo.InvariantCulture);
2024-07-26 00:02:46 +00:00
leftText = $"{dps[..^1]}.";
rightText = $"{dps[^1..]}B";
2024-07-25 06:50:32 +00:00
}
2024-07-25 03:56:10 +00:00
2024-07-26 00:02:46 +00:00
if (hasRight) {
left->SetText(leftText);
right->SetText(rightText);
} else {
left->SetText($"{leftText}{rightText}");
2024-07-25 06:50:32 +00:00
}
2024-07-25 03:49:23 +00:00
}
2024-07-24 22:04:52 +00:00
}
2024-07-24 20:30:12 +00:00
}