 b2276a1883
			
		
	
	b2276a1883
	
	
	
		
			
			* 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>
		
			
				
	
	
		
			339 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			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;
 | |
|     }
 | |
| }
 |