using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Penumbra.PlayerWatch; using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; namespace MareSynchronos.Utils { public delegate void PlayerChange(Character actor); public class DalamudUtil : IDisposable { private readonly ClientState _clientState; private readonly ObjectTable _objectTable; private readonly IPlayerWatcher _watcher; public event PlayerChange? PlayerChanged; public DalamudUtil(ClientState clientState, ObjectTable objectTable, IPlayerWatcher watcher) { _clientState = clientState; _objectTable = objectTable; _watcher = watcher; _watcher.Enable(); _watcher.PlayerChanged += WatcherOnPlayerChanged; } private void WatcherOnPlayerChanged(Character actor) { PlayerChanged?.Invoke(actor); } public void AddPlayerToWatch(string playerName) { _watcher.AddPlayerToWatch(playerName); } public void RemovePlayerFromWatch(string playerName) { _watcher.RemovePlayerFromWatch(playerName); } public bool IsPlayerPresent => _clientState.LocalPlayer != null; public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--"; public int PlayerJobId => (int)_clientState.LocalPlayer!.ClassJob.Id; public IntPtr PlayerPointer => _clientState.LocalPlayer!.Address; public string PlayerNameHashed => Crypto.GetHash256(PlayerName + _clientState.LocalPlayer!.HomeWorld.Id); public Dictionary GetLocalPlayers() { if (!_clientState.IsLoggedIn) { return new Dictionary(); } Dictionary allLocalPlayers = new(); foreach (var obj in _objectTable) { if (obj.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue; string playerName = obj.Name.ToString(); if (playerName == PlayerName) continue; var playerObject = (PlayerCharacter)obj; allLocalPlayers[Crypto.GetHash256(playerObject.Name.ToString() + playerObject.HomeWorld.Id.ToString())] = playerObject; } return allLocalPlayers; } public List GetPlayerCharacters() { return _objectTable.Where(obj => obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player && obj.Name.ToString() != PlayerName).Select(p => (PlayerCharacter)p).ToList(); } public PlayerCharacter? GetPlayerCharacterFromObjectTableIndex(int index) { var objTableObj = _objectTable[index]; if (objTableObj!.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) return null; return (PlayerCharacter)objTableObj; } public PlayerCharacter? GetPlayerCharacterFromObjectTableByName(string characterName) { foreach (var item in _objectTable) { if (item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue; if (item.Name.ToString() == characterName) return (PlayerCharacter)item; } return null; } public unsafe void WaitWhileCharacterIsDrawing(IntPtr characterAddress, CancellationToken? ct = null) { if (!_clientState.IsLoggedIn) return; var obj = (GameObject*)characterAddress; // ReSharper disable once LoopVariableIsNeverChangedInsideLoop while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true)) // 0b100000000000 is "still rendering" or something { Logger.Debug("Waiting for character to finish drawing"); Thread.Sleep(1000); } if (ct?.IsCancellationRequested ?? false) return; // wait half a second just in case Thread.Sleep(500); } public void WaitWhileSelfIsDrawing(CancellationToken token) => WaitWhileCharacterIsDrawing(_clientState.LocalPlayer?.Address ?? new IntPtr(), token); public void Dispose() { _watcher.Disable(); _watcher.Dispose(); } } }