diff --git a/Dalamud/Fools/FoolsManager.cs b/Dalamud/Fools/FoolsManager.cs index b85c2ee24..a8fa3d624 100644 --- a/Dalamud/Fools/FoolsManager.cs +++ b/Dalamud/Fools/FoolsManager.cs @@ -135,6 +135,16 @@ internal class FoolsManager : IDisposable, IServiceType RealAuthor = "Chirp", Type = typeof(GoodVibesPlugin), }, + new() + { + Name = "YesHealMe", + InternalName = "YesHealMePlugin", + Description = "The only warning you need.", + Author = "MidoriKami", + RealAuthor = "Berna", + Type = typeof(YesHealMePlugin), + }, + }; } diff --git a/Dalamud/Fools/Helper/YesHealMe/HudHelper.cs b/Dalamud/Fools/Helper/YesHealMe/HudHelper.cs new file mode 100644 index 000000000..6de258c6e --- /dev/null +++ b/Dalamud/Fools/Helper/YesHealMe/HudHelper.cs @@ -0,0 +1,41 @@ +using Dalamud; +using Dalamud.Game.ClientState.Objects; +using Dalamud.Game.ClientState.Objects.SubKinds; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; + +namespace NoTankYou.Utilities; + +public static unsafe class HudHelper +{ + private static AgentHUD* AgentHud => AgentModule.Instance()->GetAgentHUD(); + + public static PlayerCharacter? GetPlayerCharacter(int index) + { + // Sorta temporary, waiting for ClientStructs to merge a fixed size array for this element + var partyMemberList = AgentHud->PartyMemberList; + var targetOffset = index * sizeof(HudPartyMember); + var targetAddress = partyMemberList + targetOffset; + var hudData = (HudPartyMember*) targetAddress; + + var targetPlayer = hudData->ObjectId; + + return GetPlayer(targetPlayer); + } + + public static PlayerCharacter? GetAllianceMember(int index) + { + var targetPlayer = AgentHud->RaidMemberIds[index]; + + return GetPlayer(targetPlayer); + } + + private static PlayerCharacter? GetPlayer(uint objectId) + { + var result = Service.Get().SearchById(objectId); + + if (result?.GetType() == typeof(PlayerCharacter)) + return result as PlayerCharacter; + + return null; + } +} diff --git a/Dalamud/Fools/Helper/YesHealMe/PartyListAddon.cs b/Dalamud/Fools/Helper/YesHealMe/PartyListAddon.cs new file mode 100644 index 000000000..a2b106b40 --- /dev/null +++ b/Dalamud/Fools/Helper/YesHealMe/PartyListAddon.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud; +using Dalamud.Game; +using Dalamud.Game.Gui; +using FFXIVClientStructs.FFXIV.Client.UI; +using NoTankYou.DataModels; +using NoTankYou.Utilities; + +namespace NoTankYou.System; + +public unsafe class PartyListAddon : IEnumerable, IDisposable +{ + public record PartyFramePositionInfo(Vector2 Position, Vector2 Size, Vector2 Scale); + public IEnumerator GetEnumerator() => addonData.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + private static AddonPartyList* PartyList => (AddonPartyList*) Service.Get()?.GetAddonByName("_PartyList"); + public static bool DataAvailable => PartyList != null && PartyList->AtkUnitBase.RootNode != null; + + private readonly List addonData = new(); + + public PartyListAddon() + { + Service.Get().Update += OnFrameworkUpdate; + } + + public void Dispose() + { + Service.Get().Update -= OnFrameworkUpdate; + } + + private void OnFrameworkUpdate(Framework framework) + { + addonData.Clear(); + if (!DataAvailable) return; + if (PartyList->MemberCount <= 0) return; + + foreach (var index in Enumerable.Range(0, PartyList->MemberCount)) + { + var playerCharacter = HudHelper.GetPlayerCharacter(index); + var userInterface = PartyList->PartyMember[index]; + + addonData.Add(new PartyListAddonData + { + PlayerCharacter = playerCharacter, + UserInterface = userInterface, + }); + } + } + + public static PartyFramePositionInfo GetPositionInfo() + { + // Resource Node (id 9) contains a weird offset for the actual list elements + var rootNode = PartyList->AtkUnitBase.RootNode; + var addonBasePosition = new Vector2(rootNode->X, rootNode->Y); + var scale = new Vector2(rootNode->ScaleX, rootNode->ScaleY); + + var partyListNode = PartyList->AtkUnitBase.GetNodeById(9); + var partyListPositionOffset = new Vector2(partyListNode->X, partyListNode->Y) * scale; + var partyListSize = new Vector2(partyListNode->Width, partyListNode->Height); + + return new PartyFramePositionInfo(addonBasePosition + partyListPositionOffset, partyListSize * scale, scale); + } +} diff --git a/Dalamud/Fools/Helper/YesHealMe/PartyListAddonData.cs b/Dalamud/Fools/Helper/YesHealMe/PartyListAddonData.cs new file mode 100644 index 000000000..b9989ab0b --- /dev/null +++ b/Dalamud/Fools/Helper/YesHealMe/PartyListAddonData.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Dalamud.Game.ClientState.Objects.SubKinds; +using FFXIVClientStructs.FFXIV.Client.UI; + +namespace NoTankYou.DataModels; + +public readonly unsafe struct PartyListAddonData +{ + private static readonly Dictionary TimeSinceLastTargetable = new(); + + public AddonPartyList.PartyListMemberStruct UserInterface { get; init; } + public PlayerCharacter? PlayerCharacter { get; init; } + + private bool Targetable => UserInterface.PartyMemberComponent->OwnerNode->AtkResNode.Color.A != 0x99; + + public bool IsTargetable() + { + if (PlayerCharacter is null) return false; + + TimeSinceLastTargetable.TryAdd(PlayerCharacter.ObjectId, Stopwatch.StartNew()); + var stopwatch = TimeSinceLastTargetable[PlayerCharacter.ObjectId]; + + if (Targetable) + { + // Returns true if the party member has been targetable for 2second or more + return stopwatch.Elapsed >= TimeSpan.FromSeconds(2); + } + else + { + // Returns false, and continually resets the stopwatch + stopwatch.Restart(); + return false; + } + } +} \ No newline at end of file diff --git a/Dalamud/Fools/Plugins/YesHealMePlugin.cs b/Dalamud/Fools/Plugins/YesHealMePlugin.cs new file mode 100644 index 000000000..5b416b0a6 --- /dev/null +++ b/Dalamud/Fools/Plugins/YesHealMePlugin.cs @@ -0,0 +1,32 @@ +using System.Linq; +using Dalamud.Logging; +using NoTankYou.System; + +namespace Dalamud.Fools.Plugins; + +public class YesHealMePlugin: IFoolsPlugin +{ + private PartyListAddon partyListAddon; + + public YesHealMePlugin() + { + partyListAddon = new PartyListAddon(); + } + + public void DrawUi() + { + foreach (var partyMember in this.partyListAddon.Select(pla => pla.PlayerCharacter).Where(pc => pc is not null)) + { + if (partyMember.CurrentHp < partyMember.MaxHp) + { + // Do things here + } + } + } + + + public void Dispose() + { + this.partyListAddon.Dispose(); + } +}