2024-07-25 03:49:23 +00:00
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Globalization;
|
2024-07-27 23:02:14 +00:00
|
|
|
|
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-08-01 01:34:55 +00:00
|
|
|
|
using Dalamud.Utility.Signatures;
|
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-08-01 01:34:55 +00:00
|
|
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
|
|
|
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
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-08-01 01:34:55 +00:00
|
|
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
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-28 00:08:48 +00:00
|
|
|
|
// [PluginService]
|
|
|
|
|
// internal ITextureProvider TextureProvider { 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-08-01 01:34:55 +00:00
|
|
|
|
[Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? FE C2")]
|
|
|
|
|
internal unsafe delegate* unmanaged<byte*, byte, void> PlaySound;
|
|
|
|
|
|
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-08-01 01:34:55 +00:00
|
|
|
|
[PluginService]
|
|
|
|
|
private IGameInteropProvider GameInteropProvider { get; init; }
|
|
|
|
|
|
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-08-01 01:34:55 +00:00
|
|
|
|
|
|
|
|
|
this.GameInteropProvider!.InitializeFromAttributes(this);
|
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
|
|
|
|
|
2024-07-28 02:46:45 +00:00
|
|
|
|
this.HandleEvaluation();
|
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-28 02:46:45 +00:00
|
|
|
|
private unsafe void HandleEvaluation() {
|
|
|
|
|
if (
|
|
|
|
|
!this.Config.UseEvaluatorNpc
|
|
|
|
|
|| this.ClientState.LocalPlayer is not { } player
|
|
|
|
|
|| this.Client.Data == null
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-28 05:16:47 +00:00
|
|
|
|
var evaluator = this.GetEvaluator();
|
2024-07-28 02:46:45 +00:00
|
|
|
|
if (evaluator == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-28 02:48:13 +00:00
|
|
|
|
if (this.Client.Data.Combatants.Count < this.Config.EvaluationMinCombatants) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-28 02:46:45 +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?.Role == player.ClassJob.GameData?.Role;
|
|
|
|
|
})
|
|
|
|
|
.ToList();
|
2024-07-28 02:48:13 +00:00
|
|
|
|
if (combatants.Count < this.Config.EvaluationMinSameRole) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-28 02:46:45 +00:00
|
|
|
|
combatants.Sort((a, b) => a.EncDps.CompareTo(b.EncDps));
|
|
|
|
|
var youIndex = combatants.FindIndex(combatant => combatant.Name == "YOU");
|
|
|
|
|
if (youIndex == -1) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var rank = (float) (youIndex + 1) / combatants.Count;
|
|
|
|
|
var thresholds = this.Config.EvaluationThresholds.ToList();
|
|
|
|
|
thresholds.Sort((a, b) => b.Value.CompareTo(a.Value));
|
|
|
|
|
foreach (var threshold in thresholds) {
|
|
|
|
|
if (rank < threshold.Value) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-28 05:16:47 +00:00
|
|
|
|
var msg = evaluator.GetLineFor(threshold.Key, this.Config.BlendEvaluations, youIndex + 1);
|
2024-07-28 02:46:45 +00:00
|
|
|
|
if (msg != null) {
|
|
|
|
|
var bytes = msg.EncodeWithNullTerminator();
|
|
|
|
|
UIModule.Instance()->ShowBattleTalkImage(
|
|
|
|
|
Encoding.UTF8.GetBytes(evaluator.Name),
|
|
|
|
|
bytes,
|
2024-07-28 05:16:47 +00:00
|
|
|
|
this.Config.EvaluationLength,
|
2024-07-28 02:46:45 +00:00
|
|
|
|
evaluator.BattleTalkImage,
|
2024-07-28 04:40:02 +00:00
|
|
|
|
6
|
2024-07-28 02:46:45 +00:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-07-28 05:16:47 +00:00
|
|
|
|
}
|
2024-07-28 04:40:02 +00:00
|
|
|
|
|
2024-07-28 05:16:47 +00:00
|
|
|
|
internal Evaluator? GetEvaluator() {
|
|
|
|
|
if (this.Config.EvaluatorId != Evaluator.RandomId) {
|
|
|
|
|
return Evaluator.Evaluators.FirstOrDefault(e => e.Id == this.Config.EvaluatorId);
|
2024-07-28 04:40:02 +00:00
|
|
|
|
}
|
2024-07-28 05:16:47 +00:00
|
|
|
|
|
|
|
|
|
var pool = Evaluator.Evaluators
|
|
|
|
|
.Where(e => this.Config.EvaluatorsRandomEnabled.GetValueOrDefault(e.Id))
|
|
|
|
|
.ToArray();
|
|
|
|
|
return pool.Length == 0
|
|
|
|
|
? null
|
|
|
|
|
: Random.Shared.GetItems(pool, 1).GetIndexOrDefault(0);
|
2024-07-28 02:46:45 +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-08-01 01:34:55 +00:00
|
|
|
|
var names = new List<ListInfo>();
|
|
|
|
|
|
|
|
|
|
var crossInfo = InfoProxyCrossRealm.Instance();
|
|
|
|
|
if (crossInfo->IsCrossRealm != 0) {
|
|
|
|
|
var crossGroup = crossInfo->CrossRealmGroups[crossInfo->LocalPlayerGroupIndex];
|
2024-07-25 03:49:23 +00:00
|
|
|
|
|
2024-08-01 01:34:55 +00:00
|
|
|
|
for (var i = 0; i < crossGroup.GroupMemberCount && i < list->PartyMembers.Length; i++) {
|
|
|
|
|
var member = crossGroup.GroupMembers[i];
|
|
|
|
|
var name = member.NameString;
|
|
|
|
|
if (string.IsNullOrWhiteSpace(name)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (chara->GetSoftTargetId() == member.EntityId) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var info = new ListInfo {
|
|
|
|
|
Name = name,
|
|
|
|
|
IsChocobo = false,
|
|
|
|
|
Mana = null,
|
|
|
|
|
PartyListIndex = i,
|
|
|
|
|
ClassJob = member.ClassJobId,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
names.Add(info);
|
2024-07-25 03:49:23 +00:00
|
|
|
|
}
|
2024-08-01 01:34:55 +00:00
|
|
|
|
} else {
|
|
|
|
|
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;
|
|
|
|
|
}
|
2024-07-24 21:48:24 +00:00
|
|
|
|
|
2024-08-01 01:34:55 +00:00
|
|
|
|
// controller soft target not handled by hoveredindex above
|
|
|
|
|
if (chara->GetSoftTargetId() == member.EntityId) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var name = MemoryHelper.ReadStringNullTerminated((nint) member.Name);
|
|
|
|
|
var isChocobo = member.Object != null
|
|
|
|
|
&& member.Object->GetObjectKind() == ObjectKind.BattleNpc
|
|
|
|
|
&& member.Object->CompanionOwnerId == player.GameObjectId;
|
|
|
|
|
if (isChocobo) {
|
|
|
|
|
name += " (YOU)";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var info = new ListInfo {
|
|
|
|
|
Name = name,
|
|
|
|
|
IsChocobo = isChocobo,
|
|
|
|
|
Mana = member.Object != null ? member.Object->Mana : null,
|
|
|
|
|
PartyListIndex = i,
|
|
|
|
|
ClassJob = member.Object != null ? member.Object->ClassJob : null,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
names.Add(info);
|
|
|
|
|
}
|
2024-07-25 06:07:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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++) {
|
2024-08-01 01:34:55 +00:00
|
|
|
|
var info = names[i];
|
|
|
|
|
var lookupName = info.Name == playerName
|
2024-07-25 03:56:10 +00:00
|
|
|
|
? "YOU"
|
2024-08-01 01:34:55 +00:00
|
|
|
|
: info.Name;
|
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-08-01 01:34:55 +00:00
|
|
|
|
this.UpdateMember(list, info, data.Encounter, combatant, data.Combatants.Values);
|
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,
|
2024-08-01 01:34:55 +00:00
|
|
|
|
ListInfo info,
|
2024-07-28 00:08:48 +00:00
|
|
|
|
bool includeBar,
|
2024-07-27 20:54:23 +00:00
|
|
|
|
bool includeTargeted
|
|
|
|
|
) {
|
2024-08-01 01:34:55 +00:00
|
|
|
|
var unit = info.IsChocobo ? list->Chocobo : list->PartyMembers[info.PartyListIndex];
|
|
|
|
|
if (includeBar && unit.TargetGlow != null && (includeTargeted || list->TargetedIndex != info.PartyListIndex)) {
|
2024-07-27 20:54:23 +00:00
|
|
|
|
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-08-01 01:34:55 +00:00
|
|
|
|
unit.TargetGlow->SetWidth(320);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (info.IsChocobo) {
|
|
|
|
|
var chocoboTimer = list->GetTextNodeById(5);
|
|
|
|
|
var chocoboTimerIcon = list->GetTextNodeById(4);
|
|
|
|
|
|
|
|
|
|
var hasTimer = chocoboTimer != null && chocoboTimer->IsVisible();
|
|
|
|
|
var hasTimerIcon = chocoboTimerIcon != null && chocoboTimerIcon->IsVisible();
|
|
|
|
|
|
|
|
|
|
if (hasTimer) {
|
|
|
|
|
var companion = UIState.Instance()->Buddy.CompanionInfo;
|
|
|
|
|
var timeLeft = TimeSpan.FromSeconds(Math.Ceiling(companion.TimeLeft));
|
|
|
|
|
chocoboTimer->SetText($"{timeLeft.Minutes:00}:{timeLeft.Seconds:00}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hasTimerIcon) {
|
|
|
|
|
chocoboTimerIcon->SetText(SeIconChar.Clock.ToIconString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unit.MPGaugeBar == null) {
|
|
|
|
|
return;
|
2024-07-27 20:54:23 +00:00
|
|
|
|
}
|
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();
|
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,
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-01 01:34:55 +00:00
|
|
|
|
var manaString = info.Mana == null
|
|
|
|
|
? "???"
|
|
|
|
|
: info.Mana.Value.ToString(CultureInfo.InvariantCulture);
|
2024-07-27 20:54:23 +00:00
|
|
|
|
if (hasRight) {
|
|
|
|
|
if (manaString.Length <= 1) {
|
|
|
|
|
left->SetText("");
|
|
|
|
|
right->SetText(manaString);
|
|
|
|
|
} else {
|
|
|
|
|
left->SetText(manaString[..^2]);
|
|
|
|
|
right->SetText(manaString[^2..]);
|
2024-07-26 07:56:43 +00:00
|
|
|
|
}
|
2024-07-27 20:54:23 +00:00
|
|
|
|
} else {
|
|
|
|
|
left->SetText(manaString);
|
|
|
|
|
}
|
2024-07-26 07:56:43 +00:00
|
|
|
|
|
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++) {
|
2024-08-01 01:34:55 +00:00
|
|
|
|
var member = members[i];
|
|
|
|
|
var name = MemoryHelper.ReadStringNullTerminated((nint) member.Name);
|
|
|
|
|
var isChocobo = member.Object != null
|
|
|
|
|
&& this.ClientState.LocalPlayer is { } player
|
|
|
|
|
&& member.Object->GetObjectKind() == ObjectKind.BattleNpc
|
|
|
|
|
&& member.Object->CompanionOwnerId == player.GameObjectId;
|
|
|
|
|
if (isChocobo) {
|
|
|
|
|
name += " (YOU)";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var info = new ListInfo {
|
|
|
|
|
Name = name,
|
|
|
|
|
IsChocobo = isChocobo,
|
|
|
|
|
Mana = member.Object != null ? member.Object->Mana : null,
|
|
|
|
|
PartyListIndex = i,
|
|
|
|
|
ClassJob = member.Object != null ? member.Object->ClassJob : null,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.ResetMember(list, info, true, 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-28 20:35:38 +00:00
|
|
|
|
private unsafe string[] GetPartyNames(bool alliance) {
|
|
|
|
|
var group = GroupManager.Instance()->GetGroup();
|
|
|
|
|
if (group == null) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var length = group->PartyMembers.Length;
|
|
|
|
|
if (alliance) {
|
|
|
|
|
length += group->AllianceMembers.Length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var members = new string[length];
|
|
|
|
|
int i;
|
|
|
|
|
for (i = 0; i < group->PartyMembers.Length; i++) {
|
|
|
|
|
var member = group->PartyMembers[i];
|
|
|
|
|
members[i] = member.NameString;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (alliance) {
|
|
|
|
|
for (var j = 0; j < group->AllianceMembers.Length; j++) {
|
|
|
|
|
var member = group->AllianceMembers[j];
|
|
|
|
|
members[i + j] = member.NameString;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return members;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 03:56:10 +00:00
|
|
|
|
private unsafe void UpdateMember(
|
2024-07-27 20:54:23 +00:00
|
|
|
|
AddonPartyList* list,
|
2024-08-01 01:34:55 +00:00
|
|
|
|
ListInfo info,
|
2024-07-25 03:56:10 +00:00
|
|
|
|
Encounter encounter,
|
2024-07-28 20:35:38 +00:00
|
|
|
|
Combatant combatant,
|
|
|
|
|
IEnumerable<Combatant> combatants
|
2024-07-25 03:56:10 +00:00
|
|
|
|
) {
|
2024-08-01 01:34:55 +00:00
|
|
|
|
var member = info.IsChocobo ? list->Chocobo : list->PartyMembers[info.PartyListIndex];
|
2024-07-26 00:02:46 +00:00
|
|
|
|
if (this.Config.UseDpsBar && member.TargetGlow != null) {
|
2024-07-28 20:35:38 +00:00
|
|
|
|
float denominator;
|
|
|
|
|
switch (this.Config.DpsBarMode) {
|
|
|
|
|
case DpsBarMode.Encounter: {
|
2024-08-01 01:34:55 +00:00
|
|
|
|
denominator = combatants
|
|
|
|
|
.Select(c => c.EncDps)
|
|
|
|
|
.Aggregate(0f, (acc, val) => val > acc ? val : acc);
|
2024-07-28 20:35:38 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case DpsBarMode.Party: {
|
|
|
|
|
var party = this.GetPartyNames(false);
|
|
|
|
|
denominator = combatants
|
2024-08-01 01:34:55 +00:00
|
|
|
|
.Where(c => c.Name == "YOU" || party.Contains(c.Name) || c.Name.EndsWith(')'))
|
2024-07-28 20:35:38 +00:00
|
|
|
|
.Select(c => c.EncDps)
|
2024-08-01 01:34:55 +00:00
|
|
|
|
.Aggregate(0f, (acc, val) => val > acc ? val : acc);
|
2024-07-28 20:35:38 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case DpsBarMode.Alliance: {
|
|
|
|
|
var alliance = this.GetPartyNames(true);
|
|
|
|
|
denominator = combatants
|
2024-08-01 01:34:55 +00:00
|
|
|
|
.Where(c => c.Name == "YOU" || alliance.Contains(c.Name) || c.Name.EndsWith(')'))
|
2024-07-28 20:35:38 +00:00
|
|
|
|
.Select(c => c.EncDps)
|
2024-08-01 01:34:55 +00:00
|
|
|
|
.Aggregate(0f, (acc, val) => val > acc ? val : acc);
|
2024-07-28 20:35:38 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
denominator = encounter.EncDps;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 06:50:32 +00:00
|
|
|
|
member.TargetGlow->ToggleVisibility(true);
|
|
|
|
|
member.TargetGlow->SetAlpha((byte) Math.Round(this.Config.BarAlpha * 255));
|
2024-08-01 01:34:55 +00:00
|
|
|
|
// member.TargetGlow->SetScaleX(
|
|
|
|
|
// denominator == 0
|
|
|
|
|
// ? 0
|
|
|
|
|
// : combatant.EncDps / denominator
|
|
|
|
|
// );
|
|
|
|
|
member.TargetGlow->SetScaleX(1);
|
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-29 19:30:21 +00:00
|
|
|
|
member.TargetGlow->PartId = 3;
|
2024-08-01 01:34:55 +00:00
|
|
|
|
// member.TargetGlow->SetWidth(320);
|
|
|
|
|
member.TargetGlow->SetWidth(
|
|
|
|
|
(ushort) Math.Round(320 * (denominator == 0
|
|
|
|
|
? 1
|
|
|
|
|
: combatant.EncDps / denominator))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.Config.Mode == MeterMode.Mana && info.IsChocobo) {
|
|
|
|
|
var chocoboTimer = list->GetTextNodeById(5);
|
|
|
|
|
var chocoboTimerIcon = list->GetTextNodeById(4);
|
|
|
|
|
|
|
|
|
|
var hasTimer = chocoboTimer != null && chocoboTimer->IsVisible();
|
|
|
|
|
var hasTimerIcon = chocoboTimerIcon != null && chocoboTimerIcon->IsVisible();
|
|
|
|
|
|
|
|
|
|
if (this.Config.Alternate && !this._showDps) {
|
|
|
|
|
this.ResetMember(list, info, false, false);
|
|
|
|
|
} else {
|
|
|
|
|
if (hasTimer) {
|
|
|
|
|
var (leftText, rightText) = GetDpsText(combatant);
|
|
|
|
|
chocoboTimer->SetText($"{leftText}{rightText}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hasTimerIcon) {
|
|
|
|
|
chocoboTimerIcon->SetText("\ue05e");
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-25 06:50:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-01 01:34:55 +00:00
|
|
|
|
if (this.Config.Mode == MeterMode.Mana && member.MPGaugeBar != null) {
|
2024-07-25 06:50:32 +00:00
|
|
|
|
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 07:56:43 +00:00
|
|
|
|
if (!hasLeft) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-01 01:34:55 +00:00
|
|
|
|
if (this.Config.Alternate && !this._showDps && info.ClassJob is { } classJob) {
|
|
|
|
|
var isCaster = Array.IndexOf(this._manaUsers, classJob) != -1;
|
2024-07-25 06:50:32 +00:00
|
|
|
|
if (!this.Config.ManaModeAlternateOnlyManaUsers || isCaster) {
|
2024-08-01 01:34:55 +00:00
|
|
|
|
this.ResetMember(list, info, false, false);
|
2024-07-25 08:32:25 +00:00
|
|
|
|
return;
|
2024-07-25 06:50:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-01 01:34:55 +00:00
|
|
|
|
SetTextNodeColor(left);
|
|
|
|
|
if (hasRight) {
|
|
|
|
|
SetTextNodeColor(right);
|
|
|
|
|
}
|
2024-07-26 00:02:46 +00:00
|
|
|
|
|
2024-08-01 01:34:55 +00:00
|
|
|
|
var (leftText, rightText) = GetDpsText(combatant);
|
2024-07-26 00:02:46 +00:00
|
|
|
|
if (hasRight) {
|
2024-08-01 01:34:55 +00:00
|
|
|
|
left->SetText(leftText);
|
|
|
|
|
right->SetText(rightText);
|
|
|
|
|
} else {
|
|
|
|
|
left->SetText($"{leftText}{rightText}");
|
2024-07-26 00:02:46 +00:00
|
|
|
|
}
|
2024-08-01 01:34:55 +00:00
|
|
|
|
}
|
2024-07-26 00:02:46 +00:00
|
|
|
|
|
2024-08-01 01:34:55 +00:00
|
|
|
|
return;
|
2024-07-25 20:01:02 +00:00
|
|
|
|
|
2024-08-01 01:34:55 +00:00
|
|
|
|
(string, string) GetDpsText(Combatant combatant) {
|
|
|
|
|
string leftText, rightText;
|
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-08-01 01:34:55 +00:00
|
|
|
|
return (leftText, rightText);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetTextNodeColor(AtkTextNode* node) {
|
|
|
|
|
node->TextColor = new ByteColor {
|
|
|
|
|
RGBA = this.Config.TextColour,
|
|
|
|
|
};
|
|
|
|
|
node->AddRed = (byte) Math.Clamp(this.Config.TextAddRed, 0, 255);
|
|
|
|
|
node->AddGreen = (byte) Math.Clamp(this.Config.TextAddGreen, 0, 255);
|
|
|
|
|
node->AddBlue = (byte) Math.Clamp(this.Config.TextAddBlue, 0, 255);
|
|
|
|
|
node->MultiplyRed = (byte) Math.Clamp(this.Config.TextMulRed, 0, 100);
|
|
|
|
|
node->MultiplyGreen = (byte) Math.Clamp(this.Config.TextMulGreen, 0, 100);
|
|
|
|
|
node->MultiplyBlue = (byte) Math.Clamp(this.Config.TextMulBlue, 0, 100);
|
2024-07-25 03:49:23 +00:00
|
|
|
|
}
|
2024-07-24 22:04:52 +00:00
|
|
|
|
}
|
2024-07-24 20:30:12 +00:00
|
|
|
|
}
|
2024-08-01 01:34:55 +00:00
|
|
|
|
|
|
|
|
|
public class ListInfo {
|
|
|
|
|
public required string Name { get; init; }
|
|
|
|
|
public required bool IsChocobo { get; init; }
|
|
|
|
|
public required int PartyListIndex { get; init; }
|
|
|
|
|
public required uint? Mana { get; init; }
|
|
|
|
|
public required byte? ClassJob { get; init; }
|
|
|
|
|
}
|