minion/pet/companion/mount rework
This commit is contained in:
@@ -8,9 +8,9 @@ using Dalamud.Logging;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronos.FileCacheDB;
|
||||
using MareSynchronos.Interop;
|
||||
using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI;
|
||||
using MareSynchronos.WebAPI.Utils;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
@@ -42,24 +42,23 @@ public class CachedPlayer
|
||||
}
|
||||
|
||||
private bool _isDisposed = false;
|
||||
private CancellationTokenSource? _downloadCancellationTokenSource;
|
||||
private Dictionary<ObjectKind, CancellationTokenSource?> _downloadCancellationTokenSources = new();
|
||||
|
||||
private string _lastGlamourerData = string.Empty;
|
||||
|
||||
private string _originalGlamourerData = string.Empty;
|
||||
|
||||
public PlayerCharacter? PlayerCharacter { get; set; }
|
||||
public Dalamud.Game.ClientState.Objects.Types.Character? PlayerCharacter { get; set; }
|
||||
|
||||
public string? PlayerName { get; private set; }
|
||||
|
||||
public string PlayerNameHash { get; }
|
||||
private string _lastAppliedEquipmentHash = string.Empty;
|
||||
|
||||
public bool RequestedPenumbraRedraw { get; set; }
|
||||
|
||||
public bool WasVisible { get; private set; }
|
||||
|
||||
private readonly Dictionary<string, CharacterCacheDto> _cache = new();
|
||||
private readonly Dictionary<ObjectKind, CharacterCacheDto> _cache = new();
|
||||
|
||||
private CharacterEquipment? _currentCharacterEquipment;
|
||||
|
||||
@@ -69,46 +68,32 @@ public class CachedPlayer
|
||||
|
||||
Logger.Debug("Checking for files to download for player " + PlayerName);
|
||||
Logger.Debug("Hash for data is " + characterData.Hash);
|
||||
if (!_cache.ContainsKey(characterData.Hash))
|
||||
|
||||
if (!_cache.ContainsKey(characterData.ObjectKind))
|
||||
{
|
||||
_cache.Add(characterData.ObjectKind, new());
|
||||
_downloadCancellationTokenSources.Add(characterData.ObjectKind, null);
|
||||
}
|
||||
|
||||
_cache.TryGetValue(characterData.ObjectKind, out var cachedDto);
|
||||
if ((cachedDto?.Hash ?? string.Empty) != characterData.Hash)
|
||||
{
|
||||
Logger.Debug("Received total " + characterData.FileReplacements.Count + " file replacement data");
|
||||
_cache[characterData.Hash] = characterData;
|
||||
_cache[characterData.ObjectKind] = characterData;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("Had valid local cache for " + PlayerName);
|
||||
}
|
||||
_lastAppliedEquipmentHash = characterData.Hash;
|
||||
|
||||
DownloadAndApplyCharacter();
|
||||
DownloadAndApplyCharacter(characterData.ObjectKind);
|
||||
}
|
||||
|
||||
private void ApiControllerOnCharacterReceived(object? sender, CharacterReceivedEventArgs e)
|
||||
private void DownloadAndApplyCharacter(ObjectKind objectKind)
|
||||
{
|
||||
if (string.IsNullOrEmpty(PlayerName) || e.CharacterNameHash != PlayerNameHash) return;
|
||||
Logger.Debug("Received data for " + this);
|
||||
|
||||
Logger.Debug("Checking for files to download for player " + PlayerName);
|
||||
Logger.Debug("Hash for data is " + e.CharacterData.Hash);
|
||||
if (!_cache.ContainsKey(e.CharacterData.Hash))
|
||||
{
|
||||
Logger.Debug("Received total " + e.CharacterData.FileReplacements.Count + " file replacement data");
|
||||
_cache[e.CharacterData.Hash] = e.CharacterData;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("Had valid local cache for " + PlayerName);
|
||||
}
|
||||
_lastAppliedEquipmentHash = e.CharacterData.Hash;
|
||||
|
||||
DownloadAndApplyCharacter();
|
||||
}
|
||||
|
||||
private void DownloadAndApplyCharacter()
|
||||
{
|
||||
_downloadCancellationTokenSource?.Cancel();
|
||||
_downloadCancellationTokenSource = new CancellationTokenSource();
|
||||
var downloadToken = _downloadCancellationTokenSource.Token;
|
||||
_downloadCancellationTokenSources[objectKind]?.Cancel();
|
||||
_downloadCancellationTokenSources[objectKind] = new CancellationTokenSource();
|
||||
var downloadToken = _downloadCancellationTokenSources[objectKind]!.Token;
|
||||
var downloadId = _apiController.GetDownloadId();
|
||||
Task.Run(async () =>
|
||||
{
|
||||
@@ -116,7 +101,7 @@ public class CachedPlayer
|
||||
|
||||
Dictionary<string, string> moddedPaths;
|
||||
int attempts = 0;
|
||||
while ((toDownloadReplacements = TryCalculateModdedDictionary(_cache[_lastAppliedEquipmentHash], out moddedPaths)).Count > 0 && attempts++ <= 10)
|
||||
while ((toDownloadReplacements = TryCalculateModdedDictionary(out moddedPaths)).Count > 0 && attempts++ <= 10)
|
||||
{
|
||||
Logger.Debug("Downloading missing files for player " + PlayerName);
|
||||
await _apiController.DownloadFiles(downloadId, toDownloadReplacements, downloadToken);
|
||||
@@ -126,7 +111,7 @@ public class CachedPlayer
|
||||
return;
|
||||
}
|
||||
|
||||
if ((TryCalculateModdedDictionary(_cache[_lastAppliedEquipmentHash], out moddedPaths)).All(c => _apiController.ForbiddenTransfers.Any(f => f.Hash == c.Hash)))
|
||||
if ((TryCalculateModdedDictionary(out moddedPaths)).All(c => _apiController.ForbiddenTransfers.Any(f => f.Hash == c.Hash)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -142,7 +127,7 @@ public class CachedPlayer
|
||||
}
|
||||
}
|
||||
|
||||
ApplyCharacterData(_cache[_lastAppliedEquipmentHash], moddedPaths);
|
||||
ApplyCharacterData(objectKind, moddedPaths);
|
||||
}, downloadToken).ContinueWith(task =>
|
||||
{
|
||||
if (!task.IsCanceled) return;
|
||||
@@ -152,22 +137,21 @@ public class CachedPlayer
|
||||
});
|
||||
}
|
||||
|
||||
private List<FileReplacementDto> TryCalculateModdedDictionary(CharacterCacheDto cache,
|
||||
out Dictionary<string, string> moddedDictionary)
|
||||
private List<FileReplacementDto> TryCalculateModdedDictionary(out Dictionary<string, string> moddedDictionary)
|
||||
{
|
||||
List<FileReplacementDto> missingFiles = new();
|
||||
moddedDictionary = new Dictionary<string, string>();
|
||||
try
|
||||
{
|
||||
using var db = new FileCacheContext();
|
||||
foreach (var item in cache.FileReplacements)
|
||||
foreach (var item in _cache.SelectMany(c => c.Value.FileReplacements).ToList())
|
||||
{
|
||||
foreach (var gamePath in item.GamePaths)
|
||||
{
|
||||
var fileCache = db.FileCaches.FirstOrDefault(f => f.Hash == item.Hash);
|
||||
if (fileCache != null)
|
||||
{
|
||||
moddedDictionary.Add(gamePath, fileCache.Filepath);
|
||||
moddedDictionary[gamePath] = fileCache.Filepath;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -184,24 +168,57 @@ public class CachedPlayer
|
||||
return missingFiles;
|
||||
}
|
||||
|
||||
private unsafe void ApplyCharacterData(CharacterCacheDto cache, Dictionary<string, string> moddedPaths)
|
||||
private unsafe void ApplyCharacterData(ObjectKind objectKind, Dictionary<string, string> moddedPaths)
|
||||
{
|
||||
if (PlayerCharacter is null) return;
|
||||
var cache = _cache[objectKind];
|
||||
|
||||
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName!);
|
||||
var tempCollection = _ipcManager.PenumbraCreateTemporaryCollection(PlayerName!);
|
||||
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter!.Address);
|
||||
RequestedPenumbraRedraw = true;
|
||||
Logger.Debug(
|
||||
$"Request Redraw for {PlayerName}");
|
||||
_ipcManager.PenumbraSetTemporaryMods(tempCollection, moddedPaths, cache.ManipulationData);
|
||||
_ipcManager.GlamourerApplyAll(cache.GlamourerData, PlayerCharacter!);
|
||||
var minion = ((Character*)PlayerCharacter.Address)->CompanionObject;
|
||||
if (minion != null)
|
||||
_ipcManager.PenumbraSetTemporaryMods(tempCollection, moddedPaths, _cache.First().Value.ManipulationData);
|
||||
|
||||
if (objectKind == ObjectKind.Player)
|
||||
{
|
||||
var compName = new Utf8String(minion->Character.GameObject.GetName()).ToString();
|
||||
if (compName != null)
|
||||
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter!.Address);
|
||||
RequestedPenumbraRedraw = true;
|
||||
Logger.Debug(
|
||||
$"Request Redraw for {PlayerName}");
|
||||
_ipcManager.GlamourerApplyAll(cache.GlamourerData, PlayerCharacter!);
|
||||
}
|
||||
else if (objectKind == ObjectKind.Minion)
|
||||
{
|
||||
var minion = ((Character*)PlayerCharacter.Address)->CompanionObject;
|
||||
if (minion != null)
|
||||
{
|
||||
_ipcManager.PenumbraRedraw(compName);
|
||||
Logger.Debug($"Request Redraw for Minion");
|
||||
_ipcManager.GlamourerApplyAll(cache.GlamourerData, obj: (IntPtr)minion);
|
||||
}
|
||||
}
|
||||
else if (objectKind == ObjectKind.Pet)
|
||||
{
|
||||
var pet = _dalamudUtil.GetPet(PlayerCharacter.Address);
|
||||
if (pet != IntPtr.Zero)
|
||||
{
|
||||
Logger.Debug("Request Redraw for Pet");
|
||||
_ipcManager.GlamourerApplyAll(cache.GlamourerData, pet);
|
||||
}
|
||||
}
|
||||
else if (objectKind == ObjectKind.Companion)
|
||||
{
|
||||
var companion = _dalamudUtil.GetCompanion(PlayerCharacter.Address);
|
||||
if (companion != IntPtr.Zero)
|
||||
{
|
||||
Logger.Debug("Request Redraw for Companion");
|
||||
_ipcManager.GlamourerApplyAll(cache.GlamourerData, companion);
|
||||
}
|
||||
}
|
||||
else if (objectKind == ObjectKind.Mount)
|
||||
{
|
||||
var mount = ((CharaExt*)PlayerCharacter.Address)->Mount;
|
||||
if (mount != null)
|
||||
{
|
||||
Logger.Debug($"Request Redraw for Mount");
|
||||
_ipcManager.PenumbraRedraw((IntPtr)mount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,10 +234,11 @@ public class CachedPlayer
|
||||
Logger.Verbose("Restoring state for " + PlayerName);
|
||||
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
|
||||
_ipcManager.PenumbraRedrawEvent -= IpcManagerOnPenumbraRedrawEvent;
|
||||
_apiController.CharacterReceived -= ApiControllerOnCharacterReceived;
|
||||
_downloadCancellationTokenSource?.Cancel();
|
||||
_downloadCancellationTokenSource?.Dispose();
|
||||
_downloadCancellationTokenSource = null;
|
||||
foreach (var token in _downloadCancellationTokenSources)
|
||||
{
|
||||
token.Value?.Cancel();
|
||||
token.Value?.Dispose();
|
||||
}
|
||||
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName);
|
||||
if (PlayerCharacter != null && PlayerCharacter.IsValid())
|
||||
{
|
||||
@@ -283,9 +301,9 @@ public class CachedPlayer
|
||||
|
||||
private Task? _penumbraRedrawEventTask;
|
||||
|
||||
private void IpcManagerOnPenumbraRedrawEvent(object? sender, EventArgs e)
|
||||
private void IpcManagerOnPenumbraRedrawEvent(IntPtr address, int idx)
|
||||
{
|
||||
var player = _dalamudUtil.GetPlayerCharacterFromObjectTableByIndex((int)sender!);
|
||||
var player = _dalamudUtil.GetCharacterFromObjectTableByIndex(idx);
|
||||
if (player == null || player.Name.ToString() != PlayerName) return;
|
||||
if (!_penumbraRedrawEventTask?.IsCompleted ?? false) return;
|
||||
|
||||
@@ -294,10 +312,11 @@ public class CachedPlayer
|
||||
PlayerCharacter = player;
|
||||
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter.Address);
|
||||
|
||||
if (RequestedPenumbraRedraw == false && !string.IsNullOrEmpty(_lastAppliedEquipmentHash))
|
||||
if (RequestedPenumbraRedraw == false)
|
||||
{
|
||||
Logger.Warn("Unauthorized character change detected");
|
||||
DownloadAndApplyCharacter();
|
||||
// todo for everything I guess
|
||||
DownloadAndApplyCharacter(ObjectKind.Player);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using MareSynchronos.WebAPI;
|
||||
|
||||
namespace MareSynchronos.Managers
|
||||
{
|
||||
public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx);
|
||||
public class IpcManager : IDisposable
|
||||
{
|
||||
private readonly ICallGateSubscriber<int> _glamourerApiVersion;
|
||||
@@ -23,13 +24,16 @@ namespace MareSynchronos.Managers
|
||||
private readonly ICallGateSubscriber<object> _penumbraDispose;
|
||||
private readonly ICallGateSubscriber<IntPtr, int, object?> _penumbraObjectIsRedrawn;
|
||||
private readonly ICallGateSubscriber<string, int, object>? _penumbraRedraw;
|
||||
private readonly ICallGateSubscriber<GameObject, int, object>? _penumbraRedrawObject;
|
||||
private readonly ICallGateSubscriber<string, int> _penumbraRemoveTemporaryCollection;
|
||||
private readonly ICallGateSubscriber<string>? _penumbraResolveModDir;
|
||||
private readonly ICallGateSubscriber<string, string>? _penumbraResolvePlayer;
|
||||
private readonly ICallGateSubscriber<string, string[]>? _reverseResolvePlayer;
|
||||
private readonly ICallGateSubscriber<string, string, Dictionary<string, string>, string, int, int>
|
||||
_penumbraSetTemporaryMod;
|
||||
public IpcManager(DalamudPluginInterface pi)
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
|
||||
public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil)
|
||||
{
|
||||
Logger.Verbose("Creating " + nameof(IpcManager));
|
||||
|
||||
@@ -38,6 +42,7 @@ namespace MareSynchronos.Managers
|
||||
_penumbraResolvePlayer = pi.GetIpcSubscriber<string, string>("Penumbra.ResolvePlayerPath");
|
||||
_penumbraResolveModDir = pi.GetIpcSubscriber<string>("Penumbra.GetModDirectory");
|
||||
_penumbraRedraw = pi.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
|
||||
_penumbraRedrawObject = pi.GetIpcSubscriber<GameObject, int, object>("Penumbra.RedrawObject");
|
||||
_reverseResolvePlayer = pi.GetIpcSubscriber<string, string[]>("Penumbra.ReverseResolvePlayerPath");
|
||||
_penumbraApiVersion = pi.GetIpcSubscriber<(int, int)>("Penumbra.ApiVersions");
|
||||
_penumbraObjectIsRedrawn = pi.GetIpcSubscriber<IntPtr, int, object?>("Penumbra.GameObjectRedrawn");
|
||||
@@ -69,11 +74,13 @@ namespace MareSynchronos.Managers
|
||||
{
|
||||
PenumbraInitialized?.Invoke();
|
||||
}
|
||||
|
||||
this._dalamudUtil = dalamudUtil;
|
||||
}
|
||||
|
||||
public event VoidDelegate? PenumbraInitialized;
|
||||
public event VoidDelegate? PenumbraDisposed;
|
||||
public event EventHandler? PenumbraRedrawEvent;
|
||||
public event PenumbraRedrawEvent? PenumbraRedrawEvent;
|
||||
|
||||
public bool Initialized => CheckPenumbraApi();
|
||||
public bool CheckGlamourerApi()
|
||||
@@ -92,7 +99,7 @@ namespace MareSynchronos.Managers
|
||||
{
|
||||
try
|
||||
{
|
||||
return _penumbraApiVersion.InvokeFunc() is { Item1: 4, Item2: >=11 };
|
||||
return _penumbraApiVersion.InvokeFunc() is { Item1: 4, Item2: >= 11 };
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -109,6 +116,16 @@ namespace MareSynchronos.Managers
|
||||
_penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent);
|
||||
}
|
||||
|
||||
public void GlamourerApplyAll(string customization, IntPtr obj)
|
||||
{
|
||||
if (!CheckGlamourerApi()) return;
|
||||
var gameObj = _dalamudUtil.CreateGameObject(obj);
|
||||
if (gameObj != null)
|
||||
{
|
||||
_glamourerApplyAll!.InvokeAction(customization, gameObj);
|
||||
}
|
||||
}
|
||||
|
||||
public void GlamourerApplyAll(string customization, GameObject character)
|
||||
{
|
||||
if (!CheckGlamourerApi()) return;
|
||||
@@ -162,6 +179,16 @@ namespace MareSynchronos.Managers
|
||||
return _penumbraResolveModDir!.InvokeFunc();
|
||||
}
|
||||
|
||||
public void PenumbraRedraw(IntPtr obj)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return;
|
||||
var gameObj = _dalamudUtil.CreateGameObject(obj);
|
||||
if (gameObj != null)
|
||||
{
|
||||
_penumbraRedrawObject!.InvokeAction(gameObj, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void PenumbraRedraw(string actorName)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return;
|
||||
@@ -209,7 +236,7 @@ namespace MareSynchronos.Managers
|
||||
|
||||
private void RedrawEvent(IntPtr objectAddress, int objectTableIndex)
|
||||
{
|
||||
PenumbraRedrawEvent?.Invoke(objectTableIndex, EventArgs.Empty);
|
||||
PenumbraRedrawEvent?.Invoke(objectAddress, objectTableIndex);
|
||||
}
|
||||
|
||||
private void PenumbraInit()
|
||||
|
||||
@@ -71,9 +71,9 @@ public class OnlinePlayerManager : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayerManagerOnPlayerHasChanged(CharacterCacheDto characterCache)
|
||||
private void PlayerManagerOnPlayerHasChanged(CharacterCacheDto characterCache, ObjectKind objectKind)
|
||||
{
|
||||
PushCharacterData(OnlineVisiblePlayerHashes);
|
||||
PushCharacterData(OnlineVisiblePlayerHashes, objectKind);
|
||||
}
|
||||
|
||||
private void ApiControllerOnConnected()
|
||||
@@ -173,7 +173,7 @@ public class OnlinePlayerManager : IDisposable
|
||||
{
|
||||
if (_onlineCachedPlayers.Any(p => p.PlayerNameHash == characterNameHash))
|
||||
{
|
||||
PushCharacterData(new List<string>() { characterNameHash });
|
||||
PushCharacterData(new List<string>() { characterNameHash }, ObjectKind.Player);
|
||||
_playerTokenDisposal.TryGetValue(_onlineCachedPlayers.Single(p => p.PlayerNameHash == characterNameHash), out var cancellationTokenSource);
|
||||
cancellationTokenSource?.Cancel();
|
||||
return;
|
||||
@@ -242,20 +242,25 @@ public class OnlinePlayerManager : IDisposable
|
||||
if (newlyVisiblePlayers.Any())
|
||||
{
|
||||
Logger.Verbose("Has new visible players, pushing character data");
|
||||
PushCharacterData(newlyVisiblePlayers);
|
||||
PushCharacterData(newlyVisiblePlayers, ObjectKind.Player);
|
||||
PushCharacterData(newlyVisiblePlayers, ObjectKind.Pet);
|
||||
PushCharacterData(newlyVisiblePlayers, ObjectKind.Minion);
|
||||
PushCharacterData(newlyVisiblePlayers, ObjectKind.Companion);
|
||||
PushCharacterData(newlyVisiblePlayers, ObjectKind.Mount);
|
||||
}
|
||||
|
||||
_lastPlayerObjectCheck = DateTime.Now;
|
||||
}
|
||||
|
||||
private void PushCharacterData(List<string> visiblePlayers)
|
||||
private void PushCharacterData(List<string> visiblePlayers, ObjectKind objectKind)
|
||||
{
|
||||
if (visiblePlayers.Any() && _playerManager.LastSentCharacterData != null)
|
||||
_playerManager.LastSentCharacterData.TryGetValue(objectKind, out var characterData);
|
||||
if (visiblePlayers.Any() && characterData != null)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
Logger.Verbose(JsonConvert.SerializeObject(_playerManager.LastSentCharacterData, Formatting.Indented));
|
||||
await _apiController.PushCharacterData(_playerManager.LastSentCharacterData,
|
||||
Logger.Verbose(JsonConvert.SerializeObject(_playerManager.LastSentCharacterData[objectKind], Formatting.Indented));
|
||||
await _apiController.PushCharacterData(_playerManager.LastSentCharacterData[objectKind]!,
|
||||
visiblePlayers);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,10 +10,14 @@ using Penumbra.GameData.Structs;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MareSynchronos.Models;
|
||||
using MareSynchronos.Interop;
|
||||
|
||||
namespace MareSynchronos.Managers
|
||||
{
|
||||
public delegate void PlayerHasChanged(CharacterCacheDto characterCache);
|
||||
public delegate void PlayerHasChanged(CharacterCacheDto characterCache, ObjectKind objectKind);
|
||||
|
||||
public class PlayerManager : IDisposable
|
||||
{
|
||||
@@ -23,14 +27,18 @@ namespace MareSynchronos.Managers
|
||||
private readonly IpcManager _ipcManager;
|
||||
public event PlayerHasChanged? PlayerHasChanged;
|
||||
public bool SendingData { get; private set; }
|
||||
public CharacterCacheDto? LastSentCharacterData { get; private set; }
|
||||
public Dictionary<ObjectKind, CharacterCacheDto?> LastSentCharacterData { get; private set; } = new();
|
||||
|
||||
private CancellationTokenSource? _playerChangedCts;
|
||||
private Dictionary<ObjectKind, CancellationTokenSource?> _playerChangedCts = new();
|
||||
private DateTime _lastPlayerObjectCheck;
|
||||
private CharacterEquipment? _currentCharacterEquipment;
|
||||
private string _lastMinionName = string.Empty;
|
||||
private Dictionary<ObjectKind, CharacterEquipment?> _currentCharacterEquipment = new();
|
||||
|
||||
public PlayerManager(ApiController apiController, IpcManager ipcManager,
|
||||
private PlayerAttachedObject Minion;
|
||||
private PlayerAttachedObject Pet;
|
||||
private PlayerAttachedObject Companion;
|
||||
private PlayerAttachedObject Mount;
|
||||
|
||||
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
|
||||
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil)
|
||||
{
|
||||
Logger.Verbose("Creating " + nameof(PlayerManager));
|
||||
@@ -48,6 +56,11 @@ namespace MareSynchronos.Managers
|
||||
{
|
||||
ApiControllerOnConnected();
|
||||
}
|
||||
|
||||
Minion = new PlayerAttachedObject(ObjectKind.Minion, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((Character*)_dalamudUtil.PlayerPointer)->CompanionObject);
|
||||
Pet = new PlayerAttachedObject(ObjectKind.Pet, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetPet());
|
||||
Companion = new PlayerAttachedObject(ObjectKind.Companion, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetCompanion());
|
||||
Mount = new PlayerAttachedObject(ObjectKind.Mount, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((CharaExt*)_dalamudUtil.PlayerPointer)->Mount);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -61,25 +74,48 @@ namespace MareSynchronos.Managers
|
||||
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
|
||||
}
|
||||
|
||||
private unsafe void CheckAndUpdateObject(PlayerAttachedObject attachedObject)
|
||||
{
|
||||
var curPtr = attachedObject.CurrentAddress;
|
||||
if (curPtr != IntPtr.Zero)
|
||||
{
|
||||
var chara = (Character*)curPtr;
|
||||
if (attachedObject.Address == IntPtr.Zero || attachedObject.Address != curPtr
|
||||
|| attachedObject.CompareAndUpdateEquipment(chara->EquipSlotData, chara->CustomizeData)
|
||||
|| (chara->GameObject.DrawObject != null && (IntPtr)chara->GameObject.DrawObject != attachedObject.DrawObjectAddress))
|
||||
{
|
||||
Logger.Verbose(attachedObject.ObjectKind + " Changed " + curPtr);
|
||||
|
||||
attachedObject.Address = curPtr;
|
||||
attachedObject.DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject;
|
||||
attachedObject.CompareAndUpdateEquipment(chara->EquipSlotData, chara->CustomizeData);
|
||||
OnPlayerChanged(attachedObject.ObjectKind);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
attachedObject.Address = IntPtr.Zero;
|
||||
LastSentCharacterData[attachedObject.ObjectKind] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void DalamudUtilOnFrameworkUpdate()
|
||||
{
|
||||
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized || !_apiController.IsConnected) return;
|
||||
//_dalamudUtil.DebugPrintRenderFlags(_dalamudUtil.PlayerPointer);
|
||||
|
||||
if (DateTime.Now < _lastPlayerObjectCheck.AddSeconds(0.25)) return;
|
||||
|
||||
var minion = ((Character*)_dalamudUtil.PlayerPointer)->CompanionObject;
|
||||
string minionName = "";
|
||||
if (minion != null)
|
||||
|
||||
if (_dalamudUtil.IsPlayerPresent && !_currentCharacterEquipment[ObjectKind.Player]!.CompareAndUpdate(_dalamudUtil.PlayerCharacter))
|
||||
{
|
||||
minionName = new Utf8String(minion->Character.GameObject.GetName()).ToString();
|
||||
OnPlayerChanged(ObjectKind.Player);
|
||||
}
|
||||
|
||||
if (_dalamudUtil.IsPlayerPresent
|
||||
&& (!_currentCharacterEquipment!.CompareAndUpdate(_dalamudUtil.PlayerCharacter) || minionName != _lastMinionName))
|
||||
{
|
||||
_lastMinionName = minionName;
|
||||
OnPlayerChanged();
|
||||
}
|
||||
CheckAndUpdateObject(Minion);
|
||||
CheckAndUpdateObject(Pet);
|
||||
CheckAndUpdateObject(Companion);
|
||||
CheckAndUpdateObject(Mount);
|
||||
|
||||
_lastPlayerObjectCheck = DateTime.Now;
|
||||
}
|
||||
@@ -91,8 +127,8 @@ namespace MareSynchronos.Managers
|
||||
_ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent;
|
||||
_dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate;
|
||||
|
||||
_currentCharacterEquipment = new CharacterEquipment(_dalamudUtil.PlayerCharacter);
|
||||
PlayerChanged();
|
||||
_currentCharacterEquipment[ObjectKind.Player] = new CharacterEquipment(_dalamudUtil.PlayerCharacter);
|
||||
PlayerChanged(ObjectKind.Player);
|
||||
}
|
||||
|
||||
private void ApiController_Disconnected()
|
||||
@@ -101,12 +137,22 @@ namespace MareSynchronos.Managers
|
||||
|
||||
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
|
||||
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
|
||||
LastSentCharacterData = null;
|
||||
LastSentCharacterData.Clear();
|
||||
}
|
||||
|
||||
private async Task<CharacterCacheDto?> CreateFullCharacterCache(CancellationToken token)
|
||||
private async Task<CharacterCacheDto?> CreateFullCharacterCache(CancellationToken token, ObjectKind objectKind)
|
||||
{
|
||||
var cache = _characterDataFactory.BuildCharacterData();
|
||||
IntPtr pointer = objectKind switch
|
||||
{
|
||||
ObjectKind.Player => _dalamudUtil.PlayerPointer,
|
||||
ObjectKind.Minion => Minion.Address,
|
||||
ObjectKind.Pet => Pet.Address,
|
||||
ObjectKind.Companion => Companion.Address,
|
||||
ObjectKind.Mount => Mount.Address,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
var cache = _characterDataFactory.BuildCharacterData(pointer);
|
||||
if (cache == null) return null;
|
||||
CharacterCacheDto? cacheDto = null;
|
||||
|
||||
@@ -118,7 +164,7 @@ namespace MareSynchronos.Managers
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
cache.Kind = objectKind;
|
||||
cacheDto = cache.ToCharacterCacheDto();
|
||||
var json = JsonConvert.SerializeObject(cacheDto);
|
||||
|
||||
@@ -128,22 +174,44 @@ namespace MareSynchronos.Managers
|
||||
return cacheDto;
|
||||
}
|
||||
|
||||
private void IpcManager_PenumbraRedrawEvent(object? objectTableIndex, EventArgs e)
|
||||
private void IpcManager_PenumbraRedrawEvent(IntPtr address, int idx)
|
||||
{
|
||||
var player = _dalamudUtil.GetPlayerCharacterFromObjectTableByIndex((int)objectTableIndex!);
|
||||
if (player != null && player.Name.ToString() != _dalamudUtil.PlayerName) return;
|
||||
Logger.Debug("Penumbra Redraw Event for " + _dalamudUtil.PlayerName);
|
||||
PlayerChanged();
|
||||
Logger.Verbose("RedrawEvent for addr " + address);
|
||||
|
||||
if (address == _dalamudUtil.PlayerPointer)
|
||||
{
|
||||
Logger.Debug("Penumbra Redraw Event for " + _dalamudUtil.PlayerName);
|
||||
PlayerChanged(ObjectKind.Player);
|
||||
}
|
||||
|
||||
if (address == Minion.Address)
|
||||
{
|
||||
Logger.Debug("Penumbra Redraw Event for Minion");
|
||||
PlayerChanged(ObjectKind.Minion);
|
||||
}
|
||||
|
||||
if (address == Pet.Address)
|
||||
{
|
||||
Logger.Debug("Penumbra Redraw Event for Pet");
|
||||
PlayerChanged(ObjectKind.Pet);
|
||||
}
|
||||
|
||||
if (address == Companion.Address)
|
||||
{
|
||||
Logger.Debug("Penumbra Redraw Event for Companion");
|
||||
PlayerChanged(ObjectKind.Companion);
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayerChanged()
|
||||
private void PlayerChanged(ObjectKind objectKind)
|
||||
{
|
||||
if (_dalamudUtil.IsInGpose) return;
|
||||
|
||||
Logger.Debug("Player changed: " + _dalamudUtil.PlayerName);
|
||||
_playerChangedCts?.Cancel();
|
||||
_playerChangedCts = new CancellationTokenSource();
|
||||
var token = _playerChangedCts.Token;
|
||||
Logger.Debug("Object changed: " + objectKind.ToString());
|
||||
_playerChangedCts.TryGetValue(objectKind, out var cts);
|
||||
cts?.Cancel();
|
||||
_playerChangedCts[objectKind] = new CancellationTokenSource();
|
||||
var token = _playerChangedCts[objectKind]!.Token;
|
||||
|
||||
// fix for redraw from anamnesis
|
||||
while ((!_dalamudUtil.IsPlayerPresent || _dalamudUtil.PlayerName == "--") && !token.IsCancellationRequested)
|
||||
@@ -179,28 +247,29 @@ namespace MareSynchronos.Managers
|
||||
|
||||
_dalamudUtil.WaitWhileSelfIsDrawing(token);
|
||||
|
||||
var characterCache = (await CreateFullCharacterCache(token));
|
||||
var characterCache = (await CreateFullCharacterCache(token, objectKind));
|
||||
|
||||
if (characterCache == null || token.IsCancellationRequested) return;
|
||||
|
||||
if (characterCache.Hash == (LastSentCharacterData?.Hash ?? "-"))
|
||||
LastSentCharacterData.TryGetValue(objectKind, out var lastSentPlayerData);
|
||||
if (characterCache.Hash == (lastSentPlayerData?.Hash ?? string.Empty))
|
||||
{
|
||||
Logger.Debug("Not sending data, already sent");
|
||||
return;
|
||||
}
|
||||
|
||||
LastSentCharacterData = characterCache;
|
||||
PlayerHasChanged?.Invoke(characterCache);
|
||||
LastSentCharacterData[objectKind] = characterCache;
|
||||
PlayerHasChanged?.Invoke(characterCache, objectKind);
|
||||
SendingData = false;
|
||||
}, token);
|
||||
}
|
||||
|
||||
private void OnPlayerChanged()
|
||||
private void OnPlayerChanged(ObjectKind objectKind)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Logger.Debug("Watcher: PlayerChanged");
|
||||
PlayerChanged();
|
||||
PlayerChanged(objectKind);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user