Files
ClubPenguinClient/MareSynchronos/Utils/DalamudUtil.cs
rootdarkarchon b2276a1883 Client rework for API change and paradigm shift (#39)
* most of the groups refactoring on client

* register OnMethods for group stuff

* start implementing client (still pretty broken)

* finish implementing new api first iteration

* idk rework everything for pair shit (still WIP); goal is to remove PairedClients and GroupPairClients from ApiController

* move everything to PairManager, remove dictionaries from APiController

* remove admin stuff from client, cleanup

* adjust reconnection handling, add new settings, todo still to remove access from old stuff that's marked obsolete from config

* add back adding servers, fix intro ui

* fix obsolete calls

* adjust config namespace

* add UI for setting animation/sound permissions to syncshells

* add ConfigurationService to hot reload config on change from external

* move transient data cache to configuration

* add deleting service to ui

* fix saving of transient resources

* fix group pair user assignments

* halt scanner when penumbra inactive, add visible/online/offline split to individual pairs and tags

* add presence to syncshell ui

* move fullpause from config to server config

* fixes in code style

* more codestyle

* show info icon on player in shells, don't show icon when no changes from default state are made, add online notifs

* fixes to intro UI

---------

Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
2023-01-29 15:13:53 +01:00

339 lines
12 KiB
C#

using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.Gui;
using Dalamud.Game.Text.SeStringHandling;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using Lumina.Excel.GeneratedSheets;
using MareSynchronos.Delegates;
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
namespace MareSynchronos.Utils;
public class DalamudUtil : IDisposable
{
private readonly ClientState _clientState;
private readonly ObjectTable _objectTable;
private readonly Framework _framework;
private readonly Dalamud.Game.ClientState.Conditions.Condition _condition;
private readonly ChatGui _chatGui;
private readonly Dalamud.Data.DataManager _gameData;
public event VoidDelegate? LogIn;
public event VoidDelegate? LogOut;
public event VoidDelegate? FrameworkUpdate;
public event VoidDelegate? ClassJobChanged;
private uint? _classJobId = 0;
public event VoidDelegate? DelayedFrameworkUpdate;
public event VoidDelegate? ZoneSwitchStart;
public event VoidDelegate? ZoneSwitchEnd;
public event VoidDelegate? GposeStart;
public event VoidDelegate? GposeEnd;
public event VoidDelegate? GposeFrameworkUpdate;
private DateTime _delayedFrameworkUpdateCheck = DateTime.Now;
private bool _sentBetweenAreas = false;
public bool IsInGpose { get; private set; } = false;
public unsafe bool IsGameObjectPresent(IntPtr key)
{
foreach (var obj in _objectTable)
{
if (obj.Address == key)
{
return true;
}
}
return false;
}
public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework, Dalamud.Game.ClientState.Conditions.Condition condition, ChatGui chatGui,
Dalamud.Data.DataManager gameData)
{
_clientState = clientState;
_objectTable = objectTable;
_framework = framework;
_condition = condition;
_chatGui = chatGui;
_gameData = gameData;
_framework.Update += FrameworkOnUpdate;
if (IsLoggedIn)
{
_classJobId = _clientState.LocalPlayer!.ClassJob.Id;
ClientStateOnLogin(sender: null, EventArgs.Empty);
}
WorldData = new(() =>
{
return gameData.GetExcelSheet<World>(Dalamud.ClientLanguage.English)!
.Where(w => w.IsPublic && !w.Name.RawData.IsEmpty)
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
});
}
public Lazy<Dictionary<ushort, string>> WorldData { get; private set; }
public void PrintInfoChat(string message)
{
SeStringBuilder se = new SeStringBuilder().AddText("[Mare Synchronos] Info: ").AddItalics(message);
_chatGui.Print(se.BuiltString);
}
public void PrintWarnChat(string message)
{
SeStringBuilder se = new SeStringBuilder().AddText("[Mare Synchronos] ").AddUiForeground("Warning: " + message, 31).AddUiForegroundOff();
_chatGui.Print(se.BuiltString);
}
public void PrintErrorChat(string message)
{
SeStringBuilder se = new SeStringBuilder().AddText("[Mare Synchronos] ").AddUiForeground("Error: ", 534).AddItalicsOn().AddUiForeground(message, 534).AddUiForegroundOff().AddItalicsOff();
_chatGui.Print(se.BuiltString);
}
private unsafe void FrameworkOnUpdate(Framework framework)
{
if (GposeTarget != null && !IsInGpose)
{
Logger.Debug("Gpose start");
IsInGpose = true;
GposeStart?.Invoke();
}
else if (GposeTarget == null && IsInGpose)
{
Logger.Debug("Gpose end");
IsInGpose = false;
GposeEnd?.Invoke();
}
if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] || IsInGpose)
{
if (!_sentBetweenAreas)
{
Logger.Debug("Zone switch/Gpose start");
_sentBetweenAreas = true;
ZoneSwitchStart?.Invoke();
}
if (IsInGpose) GposeFrameworkUpdate?.Invoke();
return;
}
if (_sentBetweenAreas)
{
Logger.Debug("Zone switch/Gpose end");
_sentBetweenAreas = false;
ZoneSwitchEnd?.Invoke();
}
foreach (VoidDelegate? frameworkInvocation in (FrameworkUpdate?.GetInvocationList() ?? Array.Empty<VoidDelegate>()).Cast<VoidDelegate>())
{
try
{
frameworkInvocation?.Invoke();
}
catch (Exception ex)
{
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
}
}
if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return;
var localPlayer = _clientState.LocalPlayer;
if (localPlayer != null && !IsLoggedIn)
{
Logger.Debug("Logged in");
IsLoggedIn = true;
LogIn?.Invoke();
}
else if (localPlayer == null && IsLoggedIn)
{
Logger.Debug("Logged out");
IsLoggedIn = false;
LogOut?.Invoke();
}
if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid())
{
var newclassJobId = _clientState.LocalPlayer.ClassJob.Id;
if (_classJobId != newclassJobId)
{
_classJobId = newclassJobId;
ClassJobChanged?.Invoke();
}
}
foreach (VoidDelegate? frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty<VoidDelegate>()).Cast<VoidDelegate>())
{
try
{
frameworkInvocation?.Invoke();
}
catch (Exception ex)
{
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
}
}
_delayedFrameworkUpdateCheck = DateTime.Now;
}
private void ClientStateOnLogout(object? sender, EventArgs e)
{
LogOut?.Invoke();
}
private void ClientStateOnLogin(object? sender, EventArgs e)
{
LogIn?.Invoke();
}
public Dalamud.Game.ClientState.Objects.Types.GameObject? CreateGameObject(IntPtr reference)
{
return _objectTable.CreateObjectReference(reference);
}
public unsafe GameObject* GposeTarget => TargetSystem.Instance()->GPoseTarget;
public unsafe Dalamud.Game.ClientState.Objects.Types.GameObject? GposeTargetGameObject => GposeTarget == null ? null : _objectTable[GposeTarget->ObjectIndex];
public bool IsLoggedIn { get; private set; }
public bool IsPlayerPresent => _clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid();
public static bool IsObjectPresent(Dalamud.Game.ClientState.Objects.Types.GameObject? obj)
{
return obj != null && obj.IsValid();
}
public unsafe IntPtr GetMinion()
{
return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)PlayerPointer)->CompanionObject;
}
public unsafe IntPtr GetPet(IntPtr? playerPointer = null)
{
var mgr = CharacterManager.Instance();
playerPointer ??= PlayerPointer;
return (IntPtr)mgr->LookupPetByOwnerObject((BattleChara*)playerPointer);
}
public unsafe IntPtr GetCompanion(IntPtr? playerPointer = null)
{
var mgr = CharacterManager.Instance();
playerPointer ??= PlayerPointer;
return (IntPtr)mgr->LookupBuddyByOwnerObject((BattleChara*)playerPointer);
}
public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--";
public uint WorldId => _clientState.LocalPlayer!.HomeWorld.Id;
public IntPtr PlayerPointer => _clientState.LocalPlayer?.Address ?? IntPtr.Zero;
public PlayerCharacter PlayerCharacter => _clientState.LocalPlayer!;
public string PlayerNameHashed => Crypto.GetHash256(PlayerName + _clientState.LocalPlayer!.HomeWorld.Id);
public List<PlayerCharacter> GetPlayerCharacters()
{
return _objectTable.Where(obj =>
obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player &&
!string.Equals(obj.Name.ToString(), PlayerName, StringComparison.Ordinal)).Cast<PlayerCharacter>().ToList();
}
public Dalamud.Game.ClientState.Objects.Types.Character? GetCharacterFromObjectTableByIndex(int index)
{
var objTableObj = _objectTable[index];
if (objTableObj!.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) return null;
return (Dalamud.Game.ClientState.Objects.Types.Character)objTableObj;
}
public PlayerCharacter? GetPlayerCharacterFromObjectTableByName(string characterName)
{
foreach (var item in _objectTable)
{
if (item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
if (string.Equals(item.Name.ToString(), characterName, StringComparison.Ordinal)) return (PlayerCharacter)item;
}
return null;
}
public int? GetIndexFromObjectTableByName(string characterName)
{
for (int i = 0; i < _objectTable.Length; i++)
{
if (_objectTable[i] == null) continue;
if (_objectTable[i]!.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
if (string.Equals(_objectTable[i]!.Name.ToString(), characterName, StringComparison.Ordinal)) return i;
}
return null;
}
public async Task<T> RunOnFrameworkThread<T>(Func<T> func)
{
return await _framework.RunOnFrameworkThread(func).ConfigureAwait(false);
}
public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, int timeOut = 5000, CancellationToken? ct = null)
{
if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return;
var obj = (GameObject*)characterAddress;
const int tick = 250;
int curWaitTime = 0;
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true) && curWaitTime < timeOut) // 0b100000000000 is "still rendering" or something
{
Logger.Verbose($"Waiting for {name} to finish drawing");
curWaitTime += tick;
Thread.Sleep(tick);
}
if (ct?.IsCancellationRequested ?? false) return;
// wait quarter a second just in case
Thread.Sleep(tick);
}
public unsafe void DisableDraw(IntPtr characterAddress)
{
var obj = (GameObject*)characterAddress;
obj->DisableDraw();
}
public unsafe void WaitWhileGposeCharacterIsDrawing(IntPtr characterAddress, int timeOut = 5000)
{
Thread.Sleep(500);
var obj = (GameObject*)characterAddress;
const int tick = 250;
int curWaitTime = 0;
Logger.Verbose("RenderFlags:" + obj->RenderFlags.ToString("X"));
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
while (obj->RenderFlags != 0x00 && curWaitTime < timeOut)
{
Logger.Verbose($"Waiting for gpose actor to finish drawing");
curWaitTime += tick;
Thread.Sleep(tick);
}
Thread.Sleep(tick * 2);
}
public void Dispose()
{
_clientState.Login -= ClientStateOnLogin;
_clientState.Logout -= ClientStateOnLogout;
_framework.Update -= FrameworkOnUpdate;
}
}