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>
This commit is contained in:
rootdarkarchon
2023-01-29 15:13:53 +01:00
committed by GitHub
parent 616af3626a
commit b2276a1883
79 changed files with 3585 additions and 2701 deletions

View File

@@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Logging;
using Dalamud.Logging;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using MareSynchronos.API;
using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.API.Dto.User;
using MareSynchronos.FileCache;
using MareSynchronos.Models;
using MareSynchronos.Utils;
@@ -13,21 +10,30 @@ using MareSynchronos.WebAPI;
namespace MareSynchronos.Managers;
public class CachedPlayer
public class CachedPlayer : IDisposable
{
private readonly DalamudUtil _dalamudUtil;
private readonly FileCacheManager fileDbManager;
private readonly IpcManager _ipcManager;
private readonly ApiController _apiController;
private readonly DalamudUtil _dalamudUtil;
private readonly IpcManager _ipcManager;
private readonly FileCacheManager _fileDbManager;
private API.Data.CharacterData _cachedData = new();
private PlayerRelatedObject? _currentCharacterEquipment;
private CancellationTokenSource? _downloadCancellationTokenSource = new();
private bool _isVisible;
public CachedPlayer(string nameHash, IpcManager ipcManager, ApiController apiController, DalamudUtil dalamudUtil, FileCacheManager fileDbManager)
private string _lastGlamourerData = string.Empty;
private string _originalGlamourerData = string.Empty;
private Task? _penumbraRedrawEventTask;
public CachedPlayer(OnlineUserIdentDto onlineUser, IpcManager ipcManager, ApiController apiController, DalamudUtil dalamudUtil, FileCacheManager fileDbManager)
{
PlayerNameHash = nameHash;
OnlineUser = onlineUser;
_ipcManager = ipcManager;
_apiController = apiController;
_dalamudUtil = dalamudUtil;
this.fileDbManager = fileDbManager;
_fileDbManager = fileDbManager;
}
public bool IsVisible
@@ -40,35 +46,24 @@ public class CachedPlayer
}
}
private bool _isDisposed = true;
private CancellationTokenSource? _downloadCancellationTokenSource = new();
private string _lastGlamourerData = string.Empty;
private string _originalGlamourerData = string.Empty;
public OnlineUserIdentDto OnlineUser { get; set; }
public IntPtr PlayerCharacter { get; set; } = IntPtr.Zero;
public string? PlayerName { get; private set; }
public string PlayerNameHash { get; }
public string PlayerNameHash => OnlineUser.Ident;
public bool RequestedPenumbraRedraw { get; set; }
public bool WasVisible { get; private set; }
private CharacterCacheDto _cachedData = new();
private PlayerRelatedObject? _currentCharacterEquipment;
public void ApplyCharacterData(CharacterCacheDto characterData, OptionalPluginWarning warning)
public void ApplyCharacterData(API.Data.CharacterData characterData, OptionalPluginWarning warning)
{
Logger.Debug("Received data for " + this);
Logger.Debug("Checking for files to download for player " + PlayerName);
Logger.Debug("Hash for data is " + characterData.GetHashCode() + ", current cache hash is " + _cachedData.GetHashCode());
Logger.Debug("Hash for data is " + characterData.DataHash.Value + ", current cache hash is " + _cachedData.DataHash.Value);
if (characterData.GetHashCode() == _cachedData.GetHashCode()) return;
if (string.Equals(characterData.DataHash.Value, _cachedData.DataHash.Value, StringComparison.Ordinal)) return;
bool updateModdedPaths = false;
List<ObjectKind> charaDataToUpdate = new();
@@ -122,13 +117,12 @@ public class CachedPlayer
bool heelsOffsetDifferent = _cachedData.HeelsOffset != characterData.HeelsOffset;
if (heelsOffsetDifferent)
{
Logger.Debug("Updating " + objectKind);
charaDataToUpdate.Add(objectKind);
continue;
}
bool customizeDataDifferent = _cachedData.CustomizePlusData != characterData.CustomizePlusData;
bool customizeDataDifferent = !string.Equals(_cachedData.CustomizePlusData, characterData.CustomizePlusData, StringComparison.Ordinal);
if (customizeDataDifferent)
{
Logger.Debug("Updating " + objectKind);
@@ -160,6 +154,184 @@ public class CachedPlayer
DownloadAndApplyCharacter(charaDataToUpdate, updateModdedPaths);
}
public bool CheckExistence()
{
var curPlayerCharacter = _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName!)?.Address ?? IntPtr.Zero;
if (PlayerCharacter == IntPtr.Zero || PlayerCharacter != curPlayerCharacter)
{
return false;
}
_currentCharacterEquipment?.CheckAndUpdateObject();
if (_currentCharacterEquipment?.HasUnprocessedUpdate ?? false)
{
OnPlayerChanged();
}
IsVisible = true;
return true;
}
public void Dispose()
{
if (string.IsNullOrEmpty(PlayerName)) return;
Logger.Debug("Disposing " + PlayerName + " (" + OnlineUser + ")");
try
{
Logger.Verbose("Restoring state for " + PlayerName);
_ipcManager.PenumbraRedrawEvent -= IpcManagerOnPenumbraRedrawEvent;
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName);
_downloadCancellationTokenSource?.Cancel();
_downloadCancellationTokenSource?.Dispose();
_downloadCancellationTokenSource = null;
if (PlayerCharacter != IntPtr.Zero)
{
foreach (var item in _cachedData.FileReplacements)
{
RevertCustomizationData(item.Key);
}
}
}
catch (Exception ex)
{
Logger.Warn(ex.Message + Environment.NewLine + ex.StackTrace);
}
finally
{
_cachedData = new();
var tempPlayerName = PlayerName;
PlayerName = string.Empty;
PlayerCharacter = IntPtr.Zero;
IsVisible = false;
Logger.Debug("Disposing " + tempPlayerName + " complete");
}
}
public void Initialize(IntPtr character, string name)
{
IsVisible = true;
PlayerName = name;
PlayerCharacter = character;
Logger.Debug("Initializing Player " + this);
_ipcManager.PenumbraRedrawEvent += IpcManagerOnPenumbraRedrawEvent;
_originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
_currentCharacterEquipment = new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero,
() => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName)?.Address ?? IntPtr.Zero);
}
public override string ToString()
{
return OnlineUser.User.AliasOrUID + ":" + PlayerName + ":HasChar " + (PlayerCharacter != IntPtr.Zero);
}
private void ApplyBaseData(Dictionary<string, string> moddedPaths)
{
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName!);
_ipcManager.PenumbraSetTemporaryMods(PlayerName!, moddedPaths, _cachedData.ManipulationData);
}
private unsafe void ApplyCustomizationData(ObjectKind objectKind, CancellationToken ct)
{
if (PlayerCharacter == IntPtr.Zero) return;
_cachedData.GlamourerData.TryGetValue(objectKind, out var glamourerData);
switch (objectKind)
{
case ObjectKind.Player:
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 10000, ct);
ct.ThrowIfCancellationRequested();
_ipcManager.HeelsSetOffsetForPlayer(_cachedData.HeelsOffset, PlayerCharacter);
_ipcManager.CustomizePlusSetBodyScale(PlayerCharacter, _cachedData.CustomizePlusData);
RequestedPenumbraRedraw = true;
Logger.Debug(
$"Request Redraw for {PlayerName}");
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, PlayerCharacter);
}
else
{
_ipcManager.PenumbraRedraw(PlayerCharacter);
}
break;
case ObjectKind.MinionOrMount:
{
var minionOrMount = ((Character*)PlayerCharacter)->CompanionObject;
if (minionOrMount != null)
{
Logger.Debug($"Request Redraw for Minion/Mount");
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " minion or mount", (IntPtr)minionOrMount, 10000, ct);
ct.ThrowIfCancellationRequested();
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, (IntPtr)minionOrMount);
}
else
{
_ipcManager.PenumbraRedraw((IntPtr)minionOrMount);
}
}
break;
}
case ObjectKind.Pet:
{
int tick = 16;
var pet = _dalamudUtil.GetPet(PlayerCharacter);
if (pet != IntPtr.Zero)
{
var totalWait = 0;
var newPet = IntPtr.Zero;
const int maxWait = 3000;
Logger.Debug($"Request Redraw for Pet, waiting {maxWait}ms");
do
{
Thread.Sleep(tick);
totalWait += tick;
newPet = _dalamudUtil.GetPet(PlayerCharacter);
} while (newPet == pet && totalWait < maxWait);
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, newPet);
}
else
{
_ipcManager.PenumbraRedraw(newPet);
}
}
break;
}
case ObjectKind.Companion:
{
var companion = _dalamudUtil.GetCompanion(PlayerCharacter);
if (companion != IntPtr.Zero)
{
Logger.Debug("Request Redraw for Companion");
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " companion", companion, 10000, ct);
ct.ThrowIfCancellationRequested();
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, companion);
}
else
{
_ipcManager.PenumbraRedraw(companion);
}
}
break;
}
}
}
private void DownloadAndApplyCharacter(List<ObjectKind> objectKind, bool updateModdedPaths)
{
if (!objectKind.Any())
@@ -175,7 +347,7 @@ public class CachedPlayer
var downloadId = _apiController.GetDownloadId();
Task.Run(async () =>
{
List<FileReplacementDto> toDownloadReplacements;
List<FileReplacementData> toDownloadReplacements;
if (updateModdedPaths)
{
@@ -213,7 +385,6 @@ public class CachedPlayer
{
ApplyCustomizationData(kind, downloadToken);
}
}, downloadToken).ContinueWith(task =>
{
_downloadCancellationTokenSource = null;
@@ -225,138 +396,44 @@ public class CachedPlayer
});
}
private List<FileReplacementDto> TryCalculateModdedDictionary(out Dictionary<string, string> moddedDictionary)
private void IpcManagerOnPenumbraRedrawEvent(IntPtr address, int idx)
{
List<FileReplacementDto> missingFiles = new();
moddedDictionary = new Dictionary<string, string>(StringComparer.Ordinal);
try
var player = _dalamudUtil.GetCharacterFromObjectTableByIndex(idx);
if (player == null || !string.Equals(player.Name.ToString(), PlayerName, StringComparison.OrdinalIgnoreCase)) return;
if (!_penumbraRedrawEventTask?.IsCompleted ?? false) return;
_penumbraRedrawEventTask = Task.Run(() =>
{
foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => string.IsNullOrEmpty(v.FileSwapPath))).ToList())
PlayerCharacter = address;
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 10000, cts.Token);
cts.Dispose();
cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
if (RequestedPenumbraRedraw == false)
{
foreach (var gamePath in item.GamePaths)
{
var fileCache = fileDbManager.GetFileCacheByHash(item.Hash);
if (fileCache != null)
{
moddedDictionary[gamePath] = fileCache.ResolvedFilepath;
}
else
{
Logger.Verbose("Missing file: " + item.Hash);
missingFiles.Add(item);
}
}
}
foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => !string.IsNullOrEmpty(v.FileSwapPath))).ToList())
{
foreach (var gamePath in item.GamePaths)
{
Logger.Verbose("Adding file swap for " + gamePath + ":" + item.FileSwapPath);
moddedDictionary[gamePath] = item.FileSwapPath;
}
}
}
catch (Exception ex)
{
PluginLog.Error(ex, "Something went wrong during calculation replacements");
}
Logger.Debug("ModdedPaths calculated, missing files: " + missingFiles.Count);
return missingFiles;
}
private void ApplyBaseData(Dictionary<string, string> moddedPaths)
{
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName!);
_ipcManager.PenumbraSetTemporaryMods(PlayerName!, moddedPaths, _cachedData.ManipulationData);
}
private unsafe void ApplyCustomizationData(ObjectKind objectKind, CancellationToken ct)
{
if (PlayerCharacter == IntPtr.Zero) return;
_cachedData.GlamourerData.TryGetValue(objectKind, out var glamourerData);
if (objectKind == ObjectKind.Player)
{
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 10000, ct);
ct.ThrowIfCancellationRequested();
_ipcManager.HeelsSetOffsetForPlayer(_cachedData.HeelsOffset, PlayerCharacter);
_ipcManager.CustomizePlusSetBodyScale(PlayerCharacter, _cachedData.CustomizePlusData);
RequestedPenumbraRedraw = true;
Logger.Debug(
$"Request Redraw for {PlayerName}");
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, PlayerCharacter);
Logger.Debug("Unauthorized character change detected");
ApplyCustomizationData(ObjectKind.Player, cts.Token);
}
else
{
_ipcManager.PenumbraRedraw(PlayerCharacter);
RequestedPenumbraRedraw = false;
Logger.Debug(
$"Penumbra Redraw done for {PlayerName}");
}
}
else if (objectKind == ObjectKind.MinionOrMount)
{
var minionOrMount = ((Character*)PlayerCharacter)->CompanionObject;
if (minionOrMount != null)
{
Logger.Debug($"Request Redraw for Minion/Mount");
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " minion or mount", (IntPtr)minionOrMount, 10000, ct);
ct.ThrowIfCancellationRequested();
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, (IntPtr)minionOrMount);
}
else
{
_ipcManager.PenumbraRedraw((IntPtr)minionOrMount);
}
}
}
else if (objectKind == ObjectKind.Pet)
{
int tick = 16;
var pet = _dalamudUtil.GetPet(PlayerCharacter);
if (pet != IntPtr.Zero)
{
var totalWait = 0;
var newPet = IntPtr.Zero;
const int maxWait = 3000;
Logger.Debug($"Request Redraw for Pet, waiting {maxWait}ms");
cts.Dispose();
});
}
do
{
Thread.Sleep(tick);
totalWait += tick;
newPet = _dalamudUtil.GetPet(PlayerCharacter);
} while (newPet == pet && totalWait < maxWait);
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, newPet);
}
else
{
_ipcManager.PenumbraRedraw(newPet);
}
}
}
else if (objectKind == ObjectKind.Companion)
private void OnPlayerChanged()
{
Logger.Debug($"Player {PlayerName} changed, PenumbraRedraw is {RequestedPenumbraRedraw}");
_currentCharacterEquipment!.HasUnprocessedUpdate = false;
if (!RequestedPenumbraRedraw && PlayerCharacter != IntPtr.Zero)
{
var companion = _dalamudUtil.GetCompanion(PlayerCharacter);
if (companion != IntPtr.Zero)
{
Logger.Debug("Request Redraw for Companion");
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " companion", companion, 10000, ct);
ct.ThrowIfCancellationRequested();
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, companion);
}
else
{
_ipcManager.PenumbraRedraw(companion);
}
}
Logger.Debug($"Saving new Glamourer data");
_lastGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
}
}
@@ -404,129 +481,43 @@ public class CachedPlayer
}
}
public void DisposePlayer()
private List<FileReplacementData> TryCalculateModdedDictionary(out Dictionary<string, string> moddedDictionary)
{
if (_isDisposed) return;
if (string.IsNullOrEmpty(PlayerName)) return;
Logger.Debug("Disposing " + PlayerName + " (" + PlayerNameHash + ")");
_isDisposed = true;
List<FileReplacementData> missingFiles = new();
moddedDictionary = new Dictionary<string, string>(StringComparer.Ordinal);
try
{
Logger.Verbose("Restoring state for " + PlayerName);
_dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate;
_ipcManager.PenumbraRedrawEvent -= IpcManagerOnPenumbraRedrawEvent;
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName);
_downloadCancellationTokenSource?.Cancel();
_downloadCancellationTokenSource?.Dispose();
_downloadCancellationTokenSource = null;
if (PlayerCharacter != IntPtr.Zero)
foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => string.IsNullOrEmpty(v.FileSwapPath))).ToList())
{
foreach (var item in _cachedData.FileReplacements)
foreach (var gamePath in item.GamePaths)
{
RevertCustomizationData(item.Key);
var fileCache = _fileDbManager.GetFileCacheByHash(item.Hash);
if (fileCache != null)
{
moddedDictionary[gamePath] = fileCache.ResolvedFilepath;
}
else
{
Logger.Verbose("Missing file: " + item.Hash);
missingFiles.Add(item);
}
}
}
foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => !string.IsNullOrEmpty(v.FileSwapPath))).ToList())
{
foreach (var gamePath in item.GamePaths)
{
Logger.Verbose("Adding file swap for " + gamePath + ":" + item.FileSwapPath);
moddedDictionary[gamePath] = item.FileSwapPath;
}
}
}
catch (Exception ex)
{
Logger.Warn(ex.Message + Environment.NewLine + ex.StackTrace);
}
finally
{
_cachedData = new();
var tempPlayerName = PlayerName;
PlayerName = string.Empty;
PlayerCharacter = IntPtr.Zero;
IsVisible = false;
Logger.Debug("Disposing " + tempPlayerName + " complete");
}
}
public void InitializePlayer(IntPtr character, string name, CharacterCacheDto? cache, OptionalPluginWarning displayedChatWarning)
{
if (!_isDisposed) return;
IsVisible = true;
PlayerName = name;
PlayerCharacter = character;
Logger.Debug("Initializing Player " + this + " has cache: " + (cache != null));
_dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate;
_ipcManager.PenumbraRedrawEvent += IpcManagerOnPenumbraRedrawEvent;
_originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
_currentCharacterEquipment = new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero,
() => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName)?.Address ?? IntPtr.Zero);
_isDisposed = false;
if (cache != null)
{
ApplyCharacterData(cache, displayedChatWarning);
}
}
private void DalamudUtilOnDelayedFrameworkUpdate()
{
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized || !_apiController.IsConnected) return;
var curPlayerCharacter = _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName!)?.Address ?? IntPtr.Zero;
if (PlayerCharacter == IntPtr.Zero || PlayerCharacter != curPlayerCharacter)
{
DisposePlayer();
return;
}
_currentCharacterEquipment?.CheckAndUpdateObject();
if (_currentCharacterEquipment?.HasUnprocessedUpdate ?? false)
{
OnPlayerChanged();
}
IsVisible = true;
}
public override string ToString()
{
return PlayerNameHash + ":" + PlayerName + ":HasChar " + (PlayerCharacter != IntPtr.Zero);
}
private Task? _penumbraRedrawEventTask;
private void IpcManagerOnPenumbraRedrawEvent(IntPtr address, int idx)
{
var player = _dalamudUtil.GetCharacterFromObjectTableByIndex(idx);
if (player == null || !string.Equals(player.Name.ToString(), PlayerName, StringComparison.OrdinalIgnoreCase)) return;
if (!_penumbraRedrawEventTask?.IsCompleted ?? false) return;
_penumbraRedrawEventTask = Task.Run(() =>
{
PlayerCharacter = address;
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 10000, cts.Token);
cts.Dispose();
cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
if (RequestedPenumbraRedraw == false)
{
Logger.Debug("Unauthorized character change detected");
ApplyCustomizationData(ObjectKind.Player, cts.Token);
}
else
{
RequestedPenumbraRedraw = false;
Logger.Debug(
$"Penumbra Redraw done for {PlayerName}");
}
cts.Dispose();
});
}
private void OnPlayerChanged()
{
Logger.Debug($"Player {PlayerName} changed, PenumbraRedraw is {RequestedPenumbraRedraw}");
_currentCharacterEquipment!.HasUnprocessedUpdate = false;
if (!RequestedPenumbraRedraw && PlayerCharacter != IntPtr.Zero)
{
Logger.Debug($"Saving new Glamourer data");
_lastGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
PluginLog.Error(ex, "Something went wrong during calculation replacements");
}
Logger.Debug("ModdedPaths calculated, missing files: " + missingFiles.Count);
return missingFiles;
}
}

View File

@@ -1,7 +1,5 @@
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using System;
using System.Collections.Generic;
using Dalamud.Game.ClientState.Objects.Types;
using MareSynchronos.Utils;
using Action = System.Action;
@@ -9,14 +7,11 @@ using System.Collections.Concurrent;
using System.Text;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
using System.Threading.Tasks;
using MareSynchronos.Delegates;
namespace MareSynchronos.Managers;
public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx);
public delegate void HeelsOffsetChange(float change);
public delegate void PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath);
public delegate void CustomizePlusScaleChange(string? scale);
public class IpcManager : IDisposable
{
private readonly ICallGateSubscriber<int> _glamourerApiVersion;
@@ -57,10 +52,10 @@ public class IpcManager : IDisposable
private readonly ICallGateSubscriber<string?, object> _customizePlusOnScaleUpdate;
private readonly DalamudUtil _dalamudUtil;
private bool inGposeQueueMode = false;
private ConcurrentQueue<Action> actionQueue => inGposeQueueMode ? gposeActionQueue : normalQueue;
private readonly ConcurrentQueue<Action> normalQueue = new();
private readonly ConcurrentQueue<Action> gposeActionQueue = new();
private bool _inGposeQueueMode = false;
private ConcurrentQueue<Action> ActionQueue => _inGposeQueueMode ? _gposeActionQueue : _normalQueue;
private readonly ConcurrentQueue<Action> _normalQueue = new();
private readonly ConcurrentQueue<Action> _gposeActionQueue = new();
public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil)
{
@@ -121,7 +116,7 @@ public class IpcManager : IDisposable
private void HandleGposeActionQueue()
{
if (gposeActionQueue.TryDequeue(out var action))
if (_gposeActionQueue.TryDequeue(out var action))
{
if (action == null) return;
Logger.Debug("Execution action in gpose queue: " + action.Method);
@@ -131,7 +126,7 @@ public class IpcManager : IDisposable
public void ToggleGposeQueueMode(bool on)
{
inGposeQueueMode = on;
_inGposeQueueMode = on;
}
private void PenumbraModSettingChangedHandler()
@@ -141,15 +136,15 @@ public class IpcManager : IDisposable
private void ClearActionQueue()
{
actionQueue.Clear();
gposeActionQueue.Clear();
ActionQueue.Clear();
_gposeActionQueue.Clear();
}
private void ResourceLoaded(IntPtr ptr, string arg1, string arg2)
{
Task.Run(() =>
{
if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, true, System.Globalization.CultureInfo.InvariantCulture) != 0)
if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, ignoreCase: true, System.Globalization.CultureInfo.InvariantCulture) != 0)
{
PenumbraResourceLoadEvent?.Invoke(ptr, arg1, arg2);
}
@@ -158,7 +153,7 @@ public class IpcManager : IDisposable
private void HandleActionQueue()
{
if (actionQueue.TryDequeue(out var action))
if (ActionQueue.TryDequeue(out var action))
{
if (action == null) return;
Logger.Debug("Execution action in queue: " + action.Method);
@@ -169,10 +164,10 @@ public class IpcManager : IDisposable
public event VoidDelegate? PenumbraModSettingChanged;
public event VoidDelegate? PenumbraInitialized;
public event VoidDelegate? PenumbraDisposed;
public event PenumbraRedrawEvent? PenumbraRedrawEvent;
public event HeelsOffsetChange? HeelsOffsetChangeEvent;
public event PenumbraResourceLoadEvent? PenumbraResourceLoadEvent;
public event CustomizePlusScaleChange? CustomizePlusScaleChange;
public event DrawObjectDelegate? PenumbraRedrawEvent;
public event FloatDelegate? HeelsOffsetChangeEvent;
public event PenumbraFileResourceDelegate? PenumbraResourceLoadEvent;
public event StringDelegate? CustomizePlusScaleChange;
public bool Initialized => CheckPenumbraApi();
public bool CheckGlamourerApi()
@@ -228,11 +223,11 @@ public class IpcManager : IDisposable
Logger.Verbose("Disposing " + nameof(IpcManager));
int totalSleepTime = 0;
while (actionQueue.Count > 0 && totalSleepTime < 2000)
while (!ActionQueue.IsEmpty && totalSleepTime < 2000)
{
Logger.Verbose("Waiting for actionqueue to clear...");
HandleActionQueue();
System.Threading.Thread.Sleep(16);
Thread.Sleep(16);
totalSleepTime += 16;
}
@@ -244,7 +239,7 @@ public class IpcManager : IDisposable
_dalamudUtil.FrameworkUpdate -= HandleActionQueue;
_dalamudUtil.ZoneSwitchEnd -= ClearActionQueue;
_dalamudUtil.GposeFrameworkUpdate -= HandleGposeActionQueue;
actionQueue.Clear();
ActionQueue.Clear();
_penumbraGameObjectResourcePathResolved.Dispose();
_penumbraDispose.Dispose();
@@ -263,7 +258,7 @@ public class IpcManager : IDisposable
public void HeelsSetOffsetForPlayer(float offset, IntPtr character)
{
if (!CheckHeelsApi()) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj != null)
@@ -277,7 +272,7 @@ public class IpcManager : IDisposable
public void HeelsRestoreOffsetForPlayer(IntPtr character)
{
if (!CheckHeelsApi()) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj != null)
@@ -299,7 +294,7 @@ public class IpcManager : IDisposable
public void CustomizePlusSetBodyScale(IntPtr character, string scale)
{
if (!CheckCustomizePlusApi() || string.IsNullOrEmpty(scale)) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is Character c)
@@ -314,7 +309,7 @@ public class IpcManager : IDisposable
public void CustomizePlusRevert(IntPtr character)
{
if (!CheckCustomizePlusApi()) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is Character c)
@@ -328,7 +323,7 @@ public class IpcManager : IDisposable
public void GlamourerApplyAll(string? customization, IntPtr obj)
{
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(obj);
if (gameObj is Character c)
@@ -342,7 +337,7 @@ public class IpcManager : IDisposable
public void GlamourerApplyOnlyEquipment(string customization, IntPtr character)
{
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is Character c)
@@ -356,7 +351,7 @@ public class IpcManager : IDisposable
public void GlamourerApplyOnlyCustomization(string customization, IntPtr character)
{
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is Character c)
@@ -393,7 +388,7 @@ public class IpcManager : IDisposable
public void GlamourerRevertCharacterCustomization(GameObject character)
{
if (!CheckGlamourerApi()) return;
actionQueue.Enqueue(() => _glamourerRevertCustomization!.InvokeAction(character));
ActionQueue.Enqueue(() => _glamourerRevertCustomization!.InvokeAction(character));
}
public string PenumbraGetMetaManipulations()
@@ -411,7 +406,7 @@ public class IpcManager : IDisposable
public void PenumbraRedraw(IntPtr obj)
{
if (!CheckPenumbraApi()) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(obj);
if (gameObj != null)
@@ -425,13 +420,13 @@ public class IpcManager : IDisposable
public void PenumbraRedraw(string actorName)
{
if (!CheckPenumbraApi()) return;
actionQueue.Enqueue(() => _penumbraRedraw!.Invoke(actorName, RedrawType.Redraw));
ActionQueue.Enqueue(() => _penumbraRedraw!.Invoke(actorName, RedrawType.Redraw));
}
public void PenumbraRemoveTemporaryCollection(string characterName)
{
if (!CheckPenumbraApi()) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var collName = "Mare_" + characterName;
Logger.Verbose("Removing temp collection for " + collName);
@@ -464,7 +459,7 @@ public class IpcManager : IDisposable
{
if (!CheckPenumbraApi()) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var idx = _dalamudUtil.GetIndexFromObjectTableByName(characterName);
if (idx == null)
@@ -474,7 +469,7 @@ public class IpcManager : IDisposable
var collName = "Mare_" + characterName;
var ret = _penumbraCreateNamedTemporaryCollection.Invoke(collName);
Logger.Verbose("Creating Temp Collection " + collName + ", Success: " + ret);
var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx.Value, true);
var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx.Value, c: true);
Logger.Verbose("Assigning Temp Collection " + collName + " to index " + idx.Value);
foreach (var mod in modPaths)
{
@@ -511,6 +506,6 @@ public class IpcManager : IDisposable
private void PenumbraDispose()
{
PenumbraDisposed?.Invoke();
actionQueue.Clear();
ActionQueue.Clear();
}
}

View File

@@ -1,15 +1,8 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MareSynchronos.API;
using MareSynchronos.API.Data;
using MareSynchronos.API.Dto.User;
using MareSynchronos.FileCache;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using MareSynchronos.WebAPI.Utils;
namespace MareSynchronos.Managers;
@@ -17,39 +10,24 @@ public class OnlinePlayerManager : IDisposable
{
private readonly ApiController _apiController;
private readonly DalamudUtil _dalamudUtil;
private readonly IpcManager _ipcManager;
private readonly PlayerManager _playerManager;
private readonly FileCacheManager _fileDbManager;
private readonly Configuration _configuration;
private readonly ConcurrentDictionary<string, CachedPlayer> _onlineCachedPlayers = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, CharacterCacheDto> _temporaryStoredCharacterCache = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<CachedPlayer, CancellationTokenSource> _playerTokenDisposal = new();
private readonly ConcurrentDictionary<string, OptionalPluginWarning> _shownWarnings = new(StringComparer.Ordinal);
private readonly PairManager _pairManager;
private List<string> OnlineVisiblePlayerHashes => _onlineCachedPlayers.Select(p => p.Value).Where(p => p.PlayerCharacter != IntPtr.Zero)
.Select(p => p.PlayerNameHash).ToList();
public OnlinePlayerManager(ApiController apiController, DalamudUtil dalamudUtil, IpcManager ipcManager, PlayerManager playerManager, FileCacheManager fileDbManager, Configuration configuration)
public OnlinePlayerManager(ApiController apiController, DalamudUtil dalamudUtil, PlayerManager playerManager, FileCacheManager fileDbManager, PairManager pairManager)
{
Logger.Verbose("Creating " + nameof(OnlinePlayerManager));
_apiController = apiController;
_dalamudUtil = dalamudUtil;
_ipcManager = ipcManager;
_playerManager = playerManager;
_fileDbManager = fileDbManager;
_configuration = configuration;
_apiController.PairedClientOnline += ApiControllerOnPairedClientOnline;
_apiController.PairedClientOffline += ApiControllerOnPairedClientOffline;
_apiController.Connected += ApiControllerOnConnected;
_apiController.Disconnected += ApiControllerOnDisconnected;
_apiController.CharacterReceived += ApiControllerOnCharacterReceived;
_pairManager = pairManager;
_ipcManager.PenumbraDisposed += IpcManagerOnPenumbraDisposed;
_playerManager.PlayerHasChanged += PlayerManagerOnPlayerHasChanged;
_dalamudUtil.LogIn += DalamudUtilOnLogIn;
_dalamudUtil.LogOut += DalamudUtilOnLogOut;
_dalamudUtil.ZoneSwitchStart += DalamudUtilOnZoneSwitched;
if (_dalamudUtil.IsLoggedIn)
{
@@ -57,49 +35,9 @@ public class OnlinePlayerManager : IDisposable
}
}
private void DalamudUtilOnZoneSwitched()
private void PlayerManagerOnPlayerHasChanged(CharacterData characterCache)
{
DisposePlayers();
}
private void ApiControllerOnCharacterReceived(object? sender, CharacterReceivedEventArgs e)
{
if (!_shownWarnings.ContainsKey(e.CharacterNameHash)) _shownWarnings[e.CharacterNameHash] = new()
{
ShownCustomizePlusWarning = _configuration.DisableOptionalPluginWarnings,
ShownHeelsWarning = _configuration.DisableOptionalPluginWarnings,
};
if (_onlineCachedPlayers.TryGetValue(e.CharacterNameHash, out var visiblePlayer) && visiblePlayer.IsVisible)
{
Logger.Debug("Received data and applying to " + e.CharacterNameHash);
visiblePlayer.ApplyCharacterData(e.CharacterData, _shownWarnings[e.CharacterNameHash]);
}
else
{
Logger.Debug("Received data but no fitting character visible for " + e.CharacterNameHash);
_temporaryStoredCharacterCache[e.CharacterNameHash] = e.CharacterData;
}
}
private void PlayerManagerOnPlayerHasChanged(CharacterCacheDto characterCache)
{
PushCharacterData(OnlineVisiblePlayerHashes);
}
private void ApiControllerOnConnected()
{
var apiTask = _apiController.UserGetOnlineCharacters();
Task.WaitAll(apiTask);
AddInitialPairs(apiTask.Result);
_playerManager.PlayerHasChanged += PlayerManagerOnPlayerHasChanged;
}
private void DalamudUtilOnLogOut()
{
_dalamudUtil.DelayedFrameworkUpdate -= FrameworkOnUpdate;
PushCharacterData(_pairManager.VisibleUsers);
}
private void DalamudUtilOnLogIn()
@@ -107,146 +45,53 @@ public class OnlinePlayerManager : IDisposable
_dalamudUtil.DelayedFrameworkUpdate += FrameworkOnUpdate;
}
private void IpcManagerOnPenumbraDisposed()
private void DalamudUtilOnLogOut()
{
DisposePlayers();
}
private void DisposePlayers()
{
foreach (var kvp in _onlineCachedPlayers)
{
kvp.Value.DisposePlayer();
}
}
private void ApiControllerOnDisconnected()
{
RestoreAllCharacters();
_playerManager.PlayerHasChanged -= PlayerManagerOnPlayerHasChanged;
}
public void AddInitialPairs(List<string> apiTaskResult)
{
_onlineCachedPlayers.Clear();
foreach (var hash in apiTaskResult)
{
_onlineCachedPlayers.TryAdd(hash, CreateCachedPlayer(hash));
}
Logger.Verbose("Online and paired users: " + string.Join(Environment.NewLine, _onlineCachedPlayers.Select(k => k.Key)));
_dalamudUtil.DelayedFrameworkUpdate -= FrameworkOnUpdate;
}
public void Dispose()
{
Logger.Verbose("Disposing " + nameof(OnlinePlayerManager));
RestoreAllCharacters();
_apiController.PairedClientOnline -= ApiControllerOnPairedClientOnline;
_apiController.PairedClientOffline -= ApiControllerOnPairedClientOffline;
_apiController.Disconnected -= ApiControllerOnDisconnected;
_apiController.Connected -= ApiControllerOnConnected;
_ipcManager.PenumbraDisposed -= ApiControllerOnDisconnected;
_playerManager.PlayerHasChanged -= PlayerManagerOnPlayerHasChanged;
_dalamudUtil.LogIn -= DalamudUtilOnLogIn;
_dalamudUtil.LogOut -= DalamudUtilOnLogOut;
_dalamudUtil.ZoneSwitchStart -= DalamudUtilOnZoneSwitched;
_dalamudUtil.DelayedFrameworkUpdate -= FrameworkOnUpdate;
}
private void RestoreAllCharacters()
{
DisposePlayers();
_onlineCachedPlayers.Clear();
}
private void ApiControllerOnPairedClientOffline(string charHash)
{
Logger.Debug("Player offline: " + charHash);
RemovePlayer(charHash);
}
private void ApiControllerOnPairedClientOnline(string charHash)
{
Logger.Debug("Player online: " + charHash);
AddPlayer(charHash);
return;
}
private void AddPlayer(string characterNameHash)
{
if (_onlineCachedPlayers.TryGetValue(characterNameHash, out var cachedPlayer))
{
PushCharacterData(new List<string>() { characterNameHash });
_playerTokenDisposal.TryGetValue(cachedPlayer, out var cancellationTokenSource);
cancellationTokenSource?.Cancel();
return;
}
_onlineCachedPlayers.TryAdd(characterNameHash, CreateCachedPlayer(characterNameHash));
}
private void RemovePlayer(string characterHash)
{
if (!_onlineCachedPlayers.TryGetValue(characterHash, out var cachedPlayer))
{
return;
}
cachedPlayer.DisposePlayer();
_onlineCachedPlayers.TryRemove(characterHash, out _);
}
private void FrameworkOnUpdate()
{
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized || !_apiController.IsConnected) return;
if (!_dalamudUtil.IsPlayerPresent || !_apiController.IsConnected) return;
var playerCharacters = _dalamudUtil.GetPlayerCharacters();
var onlinePairs = _pairManager.OnlineUserPairs;
foreach (var pChar in playerCharacters)
{
var hashedName = Crypto.GetHash256(pChar);
if (_onlineCachedPlayers.TryGetValue(hashedName, out var existingPlayer) && !string.IsNullOrEmpty(existingPlayer.PlayerName))
{
existingPlayer.IsVisible = true;
continue;
}
var pair = _pairManager.FindPair(pChar);
if (pair == null) continue;
if (existingPlayer != null)
{
_temporaryStoredCharacterCache.TryRemove(hashedName, out var cache);
if (!_shownWarnings.ContainsKey(hashedName)) _shownWarnings[hashedName] = new()
{
ShownCustomizePlusWarning = _configuration.DisableOptionalPluginWarnings,
ShownHeelsWarning = _configuration.DisableOptionalPluginWarnings,
};
existingPlayer.InitializePlayer(pChar.Address, pChar.Name.ToString(), cache, _shownWarnings[hashedName]);
}
pair.InitializePair(pChar.Address, pChar.Name.ToString());
}
var newlyVisiblePlayers = _onlineCachedPlayers.Select(v => v.Value)
.Where(p => p.PlayerCharacter != IntPtr.Zero && p.IsVisible && !p.WasVisible).Select(p => p.PlayerNameHash)
var newlyVisiblePlayers = onlinePairs.Select(v => v.CachedPlayer)
.Where(p => p != null && p.PlayerCharacter != IntPtr.Zero && p.IsVisible && !p.WasVisible).Select(p => (UserDto)p!.OnlineUser)
.ToList();
if (newlyVisiblePlayers.Any())
{
Logger.Verbose("Has new visible players, pushing character data");
PushCharacterData(newlyVisiblePlayers);
PushCharacterData(newlyVisiblePlayers.Select(c => c.User).ToList());
}
}
private void PushCharacterData(List<string> visiblePlayers)
private void PushCharacterData(List<UserData> visiblePlayers)
{
if (visiblePlayers.Any() && _playerManager.LastCreatedCharacterData != null)
{
Task.Run(async () =>
{
await _apiController.PushCharacterData(_playerManager.LastCreatedCharacterData,
visiblePlayers).ConfigureAwait(false);
await _apiController.PushCharacterData(_playerManager.LastCreatedCharacterData, visiblePlayers).ConfigureAwait(false);
});
}
}
private CachedPlayer CreateCachedPlayer(string hashedName)
{
return new CachedPlayer(hashedName, _ipcManager, _apiController, _dalamudUtil, _fileDbManager);
}
}

View File

@@ -0,0 +1,329 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Interface;
using Dalamud.Utility;
using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Comparer;
using MareSynchronos.API.Data.Extensions;
using MareSynchronos.API.Dto.Group;
using MareSynchronos.API.Dto.User;
using MareSynchronos.Factories;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using System.Collections.Concurrent;
namespace MareSynchronos.Managers;
public class PairManager : IDisposable
{
private readonly ConcurrentDictionary<UserData, Pair> _allClientPairs = new(UserDataComparer.Instance);
private readonly ConcurrentDictionary<GroupData, GroupFullInfoDto> _allGroups = new(GroupDataComparer.Instance);
private readonly CachedPlayerFactory _cachedPlayerFactory;
private readonly DalamudUtil _dalamudUtil;
private readonly PairFactory _pairFactory;
private readonly UiBuilder _uiBuilder;
private readonly ConfigurationService _configurationService;
public PairManager(CachedPlayerFactory cachedPlayerFactory, DalamudUtil dalamudUtil, PairFactory pairFactory, UiBuilder uiBuilder, ConfigurationService configurationService)
{
_cachedPlayerFactory = cachedPlayerFactory;
_dalamudUtil = dalamudUtil;
_pairFactory = pairFactory;
_uiBuilder = uiBuilder;
_configurationService = configurationService;
_dalamudUtil.ZoneSwitchStart += DalamudUtilOnZoneSwitched;
_dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate;
_directPairsInternal = DirectPairsLazy();
_groupPairsInternal = GroupPairsLazy();
}
private void RecreateLazy()
{
_directPairsInternal = DirectPairsLazy();
_groupPairsInternal = GroupPairsLazy();
}
private Lazy<List<Pair>> _directPairsInternal;
private Lazy<Dictionary<GroupFullInfoDto, List<Pair>>> _groupPairsInternal;
public Dictionary<GroupFullInfoDto, List<Pair>> GroupPairs => _groupPairsInternal.Value;
public List<Pair> DirectPairs => _directPairsInternal.Value;
private Lazy<List<Pair>> DirectPairsLazy() => new(() => _allClientPairs.Select(k => k.Value).Where(k => k.UserPair != null).ToList());
private Lazy<Dictionary<GroupFullInfoDto, List<Pair>>> GroupPairsLazy()
{
return new Lazy<Dictionary<GroupFullInfoDto, List<Pair>>>(() =>
{
Dictionary<GroupFullInfoDto, List<Pair>> outDict = new();
foreach (var group in _allGroups)
{
outDict[group.Value] = _allClientPairs.Select(p => p.Value).Where(p => p.GroupPair.Any(g => GroupDataComparer.Instance.Equals(group.Key, g.Key.Group))).ToList();
}
return outDict;
});
}
public List<Pair> OnlineUserPairs => _allClientPairs.Where(p => !string.IsNullOrEmpty(p.Value.PlayerNameHash)).Select(p => p.Value).ToList();
public List<UserData> VisibleUsers => _allClientPairs.Where(p => p.Value.CachedPlayer != null && p.Value.CachedPlayer.IsVisible).Select(p => p.Key).ToList();
public void AddGroup(GroupFullInfoDto dto)
{
_allGroups[dto.Group] = dto;
RecreateLazy();
}
public void RemoveGroup(GroupData data)
{
_allGroups.TryRemove(data, out _);
foreach (var item in _allClientPairs.ToList())
{
foreach (var grpPair in item.Value.GroupPair.Select(k => k.Key).ToList())
{
if (GroupDataComparer.Instance.Equals(grpPair.Group, data))
{
_allClientPairs[item.Key].GroupPair.Remove(grpPair);
}
}
if (!_allClientPairs[item.Key].HasAnyConnection())
{
_allClientPairs.TryRemove(item.Key, out _);
}
}
RecreateLazy();
}
public void AddGroupPair(GroupPairFullInfoDto dto)
{
if (!_allClientPairs.ContainsKey(dto.User)) _allClientPairs[dto.User] = _pairFactory.Create();
var group = _allGroups[dto.Group];
_allClientPairs[dto.User].GroupPair[group] = dto;
RecreateLazy();
}
public void AddUserPair(UserPairDto dto)
{
if (!_allClientPairs.ContainsKey(dto.User)) _allClientPairs[dto.User] = _pairFactory.Create();
_allClientPairs[dto.User].UserPair = dto;
_allClientPairs[dto.User].ApplyLastReceivedData();
RecreateLazy();
}
public void ClearPairs()
{
Logger.Debug("Clearing all Pairs");
DisposePairs();
_allClientPairs.Clear();
_allGroups.Clear();
RecreateLazy();
}
public void Dispose()
{
_dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate;
_dalamudUtil.ZoneSwitchStart -= DalamudUtilOnZoneSwitched;
DisposePairs();
}
public void DisposePairs()
{
Logger.Debug("Disposing all Pairs");
foreach (var item in _allClientPairs)
{
item.Value.CachedPlayer?.Dispose();
}
}
public Pair? FindPair(PlayerCharacter? pChar)
{
if (pChar == null) return null;
var hash = pChar.GetHash256();
return OnlineUserPairs.Find(p => string.Equals(p.PlayerNameHash, hash, StringComparison.Ordinal));
}
public void MarkPairOffline(UserData user)
{
if (_allClientPairs.TryGetValue(user, out var pair))
{
pair.CachedPlayer?.Dispose();
pair.CachedPlayer = null;
RecreateLazy();
}
}
public void MarkPairOnline(OnlineUserIdentDto dto, ApiController controller)
{
if (!_allClientPairs.ContainsKey(dto.User)) throw new InvalidOperationException("No user found for " + dto);
if (_allClientPairs[dto.User].CachedPlayer != null) return;
if (_configurationService.Current.ShowOnlineNotifications)
{
var pair = _allClientPairs[dto.User];
if (_configurationService.Current.ShowOnlineNotificationsOnlyForIndividualPairs && pair.UserPair != null || !_configurationService.Current.ShowOnlineNotificationsOnlyForIndividualPairs)
{
_uiBuilder.AddNotification(string.Empty, "[Mare Synchronos] " + (pair.GetNote() ?? pair.UserData.AliasOrUID) + " is now online", Dalamud.Interface.Internal.Notifications.NotificationType.Info, 5000);
}
}
_allClientPairs[dto.User].CachedPlayer?.Dispose();
_allClientPairs[dto.User].CachedPlayer = _cachedPlayerFactory.Create(dto, controller);
RecreateLazy();
}
public void ReceiveCharaData(OnlineUserCharaDataDto dto)
{
if (!_allClientPairs.ContainsKey(dto.User)) throw new InvalidOperationException("No user found for " + dto.User);
var pair = _allClientPairs[dto.User];
if (!pair.PlayerName.IsNullOrEmpty())
{
pair.ApplyData(dto);
}
else
{
_allClientPairs[dto.User].LastReceivedCharacterData = dto.CharaData;
}
}
public void RemoveGroupPair(GroupPairDto dto)
{
if (_allClientPairs.TryGetValue(dto.User, out var pair))
{
var group = _allGroups[dto.Group];
pair.GroupPair.Remove(group);
if (!pair.HasAnyConnection())
{
_allClientPairs.TryRemove(dto.User, out _);
}
RecreateLazy();
}
}
public void RemoveUserPair(UserDto dto)
{
if (_allClientPairs.TryGetValue(dto.User, out var pair))
{
pair.UserPair = null;
if (!pair.HasAnyConnection())
{
_allClientPairs.TryRemove(dto.User, out _);
}
else
{
pair.ApplyLastReceivedData();
}
RecreateLazy();
}
}
public void UpdatePairPermissions(UserPermissionsDto dto)
{
if (!_allClientPairs.TryGetValue(dto.User, out var pair))
{
throw new InvalidOperationException("No such pair for " + dto);
}
if (pair.UserPair == null) throw new InvalidOperationException("No direct pair for " + dto);
pair.UserPair.OtherPermissions = dto.Permissions;
if (!pair.UserPair.OtherPermissions.IsPaired())
{
pair.ApplyLastReceivedData();
}
}
public void UpdateSelfPairPermissions(UserPermissionsDto dto)
{
if (!_allClientPairs.TryGetValue(dto.User, out var pair))
{
throw new InvalidOperationException("No such pair for " + dto);
}
if (pair.UserPair == null) throw new InvalidOperationException("No direct pair for " + dto);
pair.UserPair.OwnPermissions = dto.Permissions;
}
private void DalamudUtilOnDelayedFrameworkUpdate()
{
foreach (var player in _allClientPairs.Select(p => p.Value).Where(p => p.CachedPlayer != null && p.CachedPlayer.IsVisible).ToList())
{
if (!player.CachedPlayer!.CheckExistence())
{
player.CachedPlayer.Dispose();
}
}
}
private void DalamudUtilOnZoneSwitched()
{
DisposePairs();
}
public void SetGroupInfo(GroupInfoDto dto)
{
_allGroups[dto.Group].Group = dto.Group;
_allGroups[dto.Group].Owner = dto.Owner;
_allGroups[dto.Group].GroupPermissions = dto.GroupPermissions;
RecreateLazy();
}
internal void SetGroupPermissions(GroupPermissionDto dto)
{
var prevPermissions = _allGroups[dto.Group].GroupPermissions;
_allGroups[dto.Group].GroupPermissions = dto.Permissions;
if (prevPermissions.IsDisableAnimations() != dto.Permissions.IsDisableAnimations()
|| prevPermissions.IsDisableSounds() != dto.Permissions.IsDisableSounds())
{
RecreateLazy();
var group = _allGroups[dto.Group];
GroupPairs[group].ForEach(p => p.ApplyLastReceivedData());
}
RecreateLazy();
}
internal void SetGroupPairUserPermissions(GroupPairUserPermissionDto dto)
{
var group = _allGroups[dto.Group];
var prevPermissions = _allClientPairs[dto.User].GroupPair[group].GroupUserPermissions;
_allClientPairs[dto.User].GroupPair[group].GroupUserPermissions = dto.GroupPairPermissions;
if (prevPermissions.IsDisableAnimations() != dto.GroupPairPermissions.IsDisableAnimations()
|| prevPermissions.IsDisableSounds() != dto.GroupPairPermissions.IsDisableSounds())
{
_allClientPairs[dto.User].ApplyLastReceivedData();
}
RecreateLazy();
}
internal void SetGroupUserPermissions(GroupPairUserPermissionDto dto)
{
var prevPermissions = _allGroups[dto.Group].GroupUserPermissions;
_allGroups[dto.Group].GroupUserPermissions = dto.GroupPairPermissions;
if (prevPermissions.IsDisableAnimations() != dto.GroupPairPermissions.IsDisableAnimations()
|| prevPermissions.IsDisableSounds() != dto.GroupPairPermissions.IsDisableSounds())
{
RecreateLazy();
var group = _allGroups[dto.Group];
GroupPairs[group].ForEach(p => p.ApplyLastReceivedData());
}
RecreateLazy();
}
internal void SetGroupStatusInfo(GroupPairUserInfoDto dto)
{
_allGroups[dto.Group].GroupUserInfo = dto.GroupUserInfo;
}
internal void SetGroupPairStatusInfo(GroupPairUserInfoDto dto)
{
var group = _allGroups[dto.Group];
_allClientPairs[dto.User].GroupPair[group].GroupPairStatusInfo = dto.GroupUserInfo;
RecreateLazy();
}
}

View File

@@ -1,23 +1,17 @@
using MareSynchronos.Factories;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using System;
using System.Threading;
using System.Threading.Tasks;
using MareSynchronos.API;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using System.Collections.Generic;
using System.Linq;
using MareSynchronos.Models;
using MareSynchronos.FileCache;
using MareSynchronos.UI;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.Delegates;
#if DEBUG
using Newtonsoft.Json;
#endif
namespace MareSynchronos.Managers;
public delegate void PlayerHasChanged(CharacterCacheDto characterCache);
public class PlayerManager : IDisposable
{
@@ -28,15 +22,15 @@ public class PlayerManager : IDisposable
private readonly PeriodicFileScanner _periodicFileScanner;
private readonly SettingsUi _settingsUi;
private readonly IpcManager _ipcManager;
public event PlayerHasChanged? PlayerHasChanged;
public CharacterCacheDto? LastCreatedCharacterData { get; private set; }
public CharacterData PermanentDataCache { get; private set; } = new();
private readonly Dictionary<ObjectKind, Func<bool>> objectKindsToUpdate = new();
public event CharacterDataDelegate? PlayerHasChanged;
public API.Data.CharacterData? LastCreatedCharacterData { get; private set; }
public Models.CharacterData PermanentDataCache { get; private set; } = new();
private readonly Dictionary<ObjectKind, Func<bool>> _objectKindsToUpdate = new();
private CancellationTokenSource? _playerChangedCts = new();
private CancellationTokenSource _transientUpdateCts = new();
private List<PlayerRelatedObject> playerRelatedObjects = new();
private readonly List<PlayerRelatedObject> _playerRelatedObjects = new();
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil, TransientResourceManager transientResourceManager,
@@ -66,7 +60,7 @@ public class PlayerManager : IDisposable
ApiControllerOnConnected();
}
playerRelatedObjects = new List<PlayerRelatedObject>()
_playerRelatedObjects = new List<PlayerRelatedObject>()
{
new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.PlayerPointer),
new PlayerRelatedObject(ObjectKind.MinionOrMount, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((Character*)_dalamudUtil.PlayerPointer)->CompanionObject),
@@ -77,12 +71,12 @@ public class PlayerManager : IDisposable
private void DalamudUtilOnFrameworkUpdate()
{
_transientResourceManager.PlayerRelatedPointers = playerRelatedObjects.Select(f => f.CurrentAddress).ToArray();
_transientResourceManager.PlayerRelatedPointers = _playerRelatedObjects.Select(f => f.CurrentAddress).ToArray();
}
public void HandleTransientResourceLoad(IntPtr gameObj)
public void HandleTransientResourceLoad(IntPtr gameObj, int idx)
{
foreach (var obj in playerRelatedObjects)
foreach (var obj in _playerRelatedObjects)
{
if (obj.Address == gameObj && !obj.HasUnprocessedUpdate)
{
@@ -105,7 +99,7 @@ public class PlayerManager : IDisposable
private void HeelsOffsetChanged(float change)
{
var player = playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
var player = _playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
if (LastCreatedCharacterData != null && LastCreatedCharacterData.HeelsOffset != change && !player.IsProcessing)
{
Logger.Debug("Heels offset changed to " + change);
@@ -116,8 +110,8 @@ public class PlayerManager : IDisposable
private void CustomizePlusChanged(string? change)
{
change ??= string.Empty;
var player = playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
if (LastCreatedCharacterData != null && LastCreatedCharacterData.CustomizePlusData != change && !player.IsProcessing)
var player = _playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
if (LastCreatedCharacterData != null && !string.Equals(LastCreatedCharacterData.CustomizePlusData, change, StringComparison.Ordinal) && !player.IsProcessing)
{
Logger.Debug("CustomizePlus data changed to " + change);
player.HasTransientsUpdate = true;
@@ -146,8 +140,8 @@ public class PlayerManager : IDisposable
{
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return;
playerRelatedObjects.ForEach(k => k.CheckAndUpdateObject());
if (playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && !c.IsProcessing))
_playerRelatedObjects.ForEach(k => k.CheckAndUpdateObject());
if (_playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && !c.IsProcessing))
{
OnPlayerOrAttachedObjectsChanged();
}
@@ -167,9 +161,9 @@ public class PlayerManager : IDisposable
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
}
private async Task<CharacterCacheDto?> CreateFullCharacterCacheDto(CancellationToken token)
private async Task<API.Data.CharacterData?> CreateFullCharacterCacheDto(CancellationToken token)
{
foreach (var unprocessedObject in playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList())
foreach (var unprocessedObject in _playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList())
{
Logger.Verbose("Building Cache for " + unprocessedObject.ObjectKind);
PermanentDataCache = _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject, token);
@@ -194,7 +188,7 @@ public class PlayerManager : IDisposable
Logger.Verbose("Cache creation complete");
var cache = PermanentDataCache.ToCharacterCacheDto();
var cache = PermanentDataCache.ToAPI();
//Logger.Verbose(JsonConvert.SerializeObject(cache, Formatting.Indented));
return cache;
}
@@ -203,7 +197,7 @@ public class PlayerManager : IDisposable
{
Logger.Verbose("RedrawEvent for addr " + address);
foreach (var item in playerRelatedObjects)
foreach (var item in _playerRelatedObjects)
{
if (address == item.Address)
{
@@ -212,7 +206,7 @@ public class PlayerManager : IDisposable
}
}
if (playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && (!c.IsProcessing || (c.IsProcessing && c.DoNotSendUpdate))))
if (_playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && (!c.IsProcessing || (c.IsProcessing && c.DoNotSendUpdate))))
{
OnPlayerOrAttachedObjectsChanged();
}
@@ -220,7 +214,7 @@ public class PlayerManager : IDisposable
private void OnPlayerOrAttachedObjectsChanged()
{
var unprocessedObjects = playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList();
var unprocessedObjects = _playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList();
foreach (var unprocessedObject in unprocessedObjects)
{
unprocessedObject.IsProcessing = true;
@@ -253,7 +247,7 @@ public class PlayerManager : IDisposable
Task.Run(async () =>
{
CharacterCacheDto? cacheDto = null;
API.Data.CharacterData? cacheDto = null;
try
{
_periodicFileScanner.HaltScan("Character creation");
@@ -278,15 +272,13 @@ public class PlayerManager : IDisposable
//Logger.Verbose(json);
#endif
if ((LastCreatedCharacterData?.GetHashCode() ?? 0) == cacheDto.GetHashCode())
if (string.Equals(LastCreatedCharacterData?.DataHash.Value ?? string.Empty, cacheDto.DataHash.Value, StringComparison.Ordinal))
{
Logger.Debug("Not sending data, already sent");
return;
}
else
{
LastCreatedCharacterData = cacheDto;
}
LastCreatedCharacterData = cacheDto;
if (_apiController.IsConnected && !token.IsCancellationRequested && !doNotSendUpdate)
{

View File

@@ -0,0 +1,168 @@
using MareSynchronos.MareConfiguration;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos.Managers;
public class ServerConfigurationManager
{
private readonly Dictionary<JwtCache, string> _tokenDictionary = new();
private readonly ConfigurationService _configService;
private readonly DalamudUtil _dalamudUtil;
public string CurrentApiUrl => string.IsNullOrEmpty(_configService.Current.CurrentServer) ? ApiController.MainServiceUri : _configService.Current.CurrentServer;
public ServerStorage? CurrentServer => (_configService.Current.ServerStorage.ContainsKey(CurrentApiUrl) ? _configService.Current.ServerStorage[CurrentApiUrl] : null);
public ServerConfigurationManager(ConfigurationService configService, DalamudUtil dalamudUtil)
{
_configService = configService;
_dalamudUtil = dalamudUtil;
}
public bool HasValidConfig()
{
return CurrentServer != null && (CurrentServer?.Authentications.Any() ?? false);
}
public string[] GetServerApiUrls()
{
return _configService.Current.ServerStorage.Keys.ToArray();
}
public string[] GetServerNames()
{
return _configService.Current.ServerStorage.Values.Select(v => v.ServerName).ToArray();
}
public ServerStorage GetServerByIndex(int idx)
{
try
{
return _configService.Current.ServerStorage.ElementAt(idx).Value;
}
catch
{
_configService.Current.CurrentServer = ApiController.MainServiceUri;
if (!_configService.Current.ServerStorage.ContainsKey(ApiController.MainServer))
{
_configService.Current.ServerStorage.Add(_configService.Current.CurrentServer, new ServerStorage() { ServerUri = ApiController.MainServiceUri, ServerName = ApiController.MainServer });
}
_configService.Save();
return CurrentServer!;
}
}
public int GetCurrentServerIndex()
{
return Array.IndexOf(_configService.Current.ServerStorage.Keys.ToArray(), CurrentApiUrl);
}
public void Save()
{
_configService.Save();
}
public void SelectServer(int idx)
{
_configService.Current.CurrentServer = GetServerByIndex(idx).ServerUri;
CurrentServer!.FullPause = false;
Save();
}
public string? GetSecretKey(int serverIdx = -1)
{
ServerStorage? currentServer;
currentServer = serverIdx == -1 ? CurrentServer : GetServerByIndex(serverIdx);
Save();
if (currentServer == null)
{
currentServer = new();
Save();
}
var charaName = _dalamudUtil.PlayerName;
var worldId = _dalamudUtil.WorldId;
if (!currentServer.Authentications.Any() && currentServer.SecretKeys.Any())
{
currentServer.Authentications.Add(new Authentication()
{
CharacterName = charaName,
WorldId = worldId,
SecretKeyIdx = currentServer.SecretKeys.Last().Key,
});
Save();
}
var auth = currentServer.Authentications.Find(f => string.Equals(f.CharacterName, charaName, StringComparison.Ordinal) && f.WorldId == worldId);
if (auth == null) return null;
if (currentServer.SecretKeys.TryGetValue(auth.SecretKeyIdx, out var secretKey))
{
return secretKey.Key;
}
return null;
}
public string? GetToken()
{
var charaName = _dalamudUtil.PlayerName;
var worldId = _dalamudUtil.WorldId;
var secretKey = GetSecretKey();
if (secretKey == null) return null;
if (_tokenDictionary.TryGetValue(new JwtCache(CurrentApiUrl, charaName, worldId, secretKey), out var token))
{
return token;
}
return null;
}
public void SaveToken(string token)
{
var charaName = _dalamudUtil.PlayerName;
var worldId = _dalamudUtil.WorldId;
var secretKey = GetSecretKey();
if (string.IsNullOrEmpty(secretKey)) throw new InvalidOperationException("No secret key set");
_tokenDictionary[new JwtCache(CurrentApiUrl, charaName, worldId, secretKey)] = token;
}
internal void AddCurrentCharacterToServer(int serverSelectionIndex = -1, bool addLastSecretKey = false)
{
if (serverSelectionIndex == -1) serverSelectionIndex = GetCurrentServerIndex();
var server = GetServerByIndex(serverSelectionIndex);
server.Authentications.Add(new Authentication()
{
CharacterName = _dalamudUtil.PlayerName,
WorldId = _dalamudUtil.WorldId,
SecretKeyIdx = addLastSecretKey ? server.SecretKeys.Last().Key : -1,
});
_configService.Save();
}
internal void AddEmptyCharacterToServer(int serverSelectionIndex)
{
var server = GetServerByIndex(serverSelectionIndex);
server.Authentications.Add(new Authentication());
_configService.Save();
}
internal void RemoveCharacterFromServer(int serverSelectionIndex, Authentication item)
{
var server = GetServerByIndex(serverSelectionIndex);
server.Authentications.Remove(item);
}
internal void AddServer(ServerStorage serverStorage)
{
_configService.Current.ServerStorage[serverStorage.ServerUri] = serverStorage;
_configService.Save();
}
internal void DeleteServer(ServerStorage selectedServer)
{
_configService.Current.ServerStorage.Remove(selectedServer.ServerUri);
}
}

View File

@@ -1,65 +1,70 @@
using MareSynchronos.API;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.Delegates;
using MareSynchronos.Factories;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace MareSynchronos.Managers;
public delegate void TransientResourceLoadedEvent(IntPtr drawObject);
public class TransientResourceManager : IDisposable
{
private readonly IpcManager manager;
private readonly DalamudUtil dalamudUtil;
private readonly string configurationDirectory;
public event TransientResourceLoadedEvent? TransientResourceLoaded;
private readonly IpcManager _ipcManager;
private readonly ConfigurationService _configurationService;
private readonly DalamudUtil _dalamudUtil;
public event DrawObjectDelegate? TransientResourceLoaded;
public IntPtr[] PlayerRelatedPointers = Array.Empty<IntPtr>();
private readonly string[] FileTypesToHandle = new[] { "tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk" };
private string PersistentDataCache => Path.Combine(configurationDirectory, "PersistentTransientData.lst");
private readonly string[] _fileTypesToHandle = new[] { "tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk" };
[Obsolete]
private string PersistentDataCache => Path.Combine(_configurationService.ConfigurationDirectory, "PersistentTransientData.lst");
private string PlayerPersistentDataKey => _dalamudUtil.PlayerName + "_" + _dalamudUtil.WorldId;
private ConcurrentDictionary<IntPtr, HashSet<string>> TransientResources { get; } = new();
private ConcurrentDictionary<ObjectKind, HashSet<FileReplacement>> SemiTransientResources { get; } = new();
public TransientResourceManager(IpcManager manager, DalamudUtil dalamudUtil, FileReplacementFactory fileReplacementFactory, string configurationDirectory)
public TransientResourceManager(IpcManager manager, ConfigurationService configurationService, DalamudUtil dalamudUtil, FileReplacementFactory fileReplacementFactory)
{
manager.PenumbraResourceLoadEvent += Manager_PenumbraResourceLoadEvent;
manager.PenumbraModSettingChanged += Manager_PenumbraModSettingChanged;
this.manager = manager;
this.dalamudUtil = dalamudUtil;
this.configurationDirectory = configurationDirectory;
_ipcManager = manager;
_configurationService = configurationService;
_dalamudUtil = dalamudUtil;
dalamudUtil.FrameworkUpdate += DalamudUtil_FrameworkUpdate;
dalamudUtil.ClassJobChanged += DalamudUtil_ClassJobChanged;
// migrate obsolete data to new format
if (File.Exists(PersistentDataCache))
{
var persistentEntities = File.ReadAllLines(PersistentDataCache);
SemiTransientResources.TryAdd(ObjectKind.Player, new HashSet<FileReplacement>());
var persistentEntities = File.ReadAllLines(PersistentDataCache).ToHashSet(StringComparer.OrdinalIgnoreCase);
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = persistentEntities;
_configurationService.Save();
File.Delete(PersistentDataCache);
}
SemiTransientResources.TryAdd(ObjectKind.Player, new HashSet<FileReplacement>());
if (_configurationService.Current.PlayerPersistentTransientCache.TryGetValue(PlayerPersistentDataKey, out var linesInConfig))
{
int restored = 0;
foreach (var line in persistentEntities)
foreach (var file in linesInConfig)
{
try
{
var fileReplacement = fileReplacementFactory.Create();
fileReplacement.ResolvePath(line);
fileReplacement.ResolvePath(file);
if (fileReplacement.HasFileReplacement)
{
Logger.Debug("Loaded persistent transient resource " + line);
Logger.Debug("Loaded persistent transient resource " + file);
SemiTransientResources[ObjectKind.Player].Add(fileReplacement);
restored++;
}
}
catch (Exception ex)
{
Logger.Warn("Error during loading persistent transient resource " + line, ex);
Logger.Warn("Error during loading persistent transient resource " + file, ex);
}
}
Logger.Debug($"Restored {restored}/{persistentEntities.Count()} semi persistent resources");
}
Logger.Debug($"Restored {restored}/{linesInConfig.Count()} semi persistent resources");
}
}
@@ -78,7 +83,7 @@ public class TransientResourceManager : IDisposable
return !verified;
});
if (!successfulValidation)
TransientResourceLoaded?.Invoke(dalamudUtil.PlayerPointer);
TransientResourceLoaded?.Invoke(_dalamudUtil.PlayerPointer, -1);
}
});
}
@@ -95,7 +100,7 @@ public class TransientResourceManager : IDisposable
{
foreach (var item in TransientResources.ToList())
{
if (!dalamudUtil.IsGameObjectPresent(item.Key))
if (!_dalamudUtil.IsGameObjectPresent(item.Key))
{
Logger.Debug("Object not present anymore: " + item.Key.ToString("X"));
TransientResources.TryRemove(item.Key, out _);
@@ -133,7 +138,7 @@ public class TransientResourceManager : IDisposable
private void Manager_PenumbraResourceLoadEvent(IntPtr gameObject, string gamePath, string filePath)
{
if (!FileTypesToHandle.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase)))
if (!_fileTypesToHandle.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase)))
{
return;
}
@@ -171,7 +176,7 @@ public class TransientResourceManager : IDisposable
{
TransientResources[gameObject].Add(replacedGamePath);
Logger.Debug($"Adding {replacedGamePath} for {gameObject} ({filePath})");
TransientResourceLoaded?.Invoke(gameObject);
TransientResourceLoaded?.Invoke(gameObject, -1);
}
}
@@ -210,9 +215,9 @@ public class TransientResourceManager : IDisposable
try
{
var fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), true);
var fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), arg2: true);
if (!fileReplacement.HasFileReplacement)
fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), false);
fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), arg2: false);
if (fileReplacement.HasFileReplacement)
{
Logger.Debug("Persisting " + gamePath.ToLowerInvariant());
@@ -234,22 +239,26 @@ public class TransientResourceManager : IDisposable
if (objectKind == ObjectKind.Player && SemiTransientResources.TryGetValue(ObjectKind.Player, out var fileReplacements))
{
File.WriteAllLines(PersistentDataCache, fileReplacements.SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase));
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey]
= fileReplacements.SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase).ToHashSet(StringComparer.OrdinalIgnoreCase);
_configurationService.Save();
}
TransientResources[gameObject].Clear();
}
public void Dispose()
{
dalamudUtil.FrameworkUpdate -= DalamudUtil_FrameworkUpdate;
manager.PenumbraResourceLoadEvent -= Manager_PenumbraResourceLoadEvent;
dalamudUtil.ClassJobChanged -= DalamudUtil_ClassJobChanged;
manager.PenumbraModSettingChanged -= Manager_PenumbraModSettingChanged;
_dalamudUtil.FrameworkUpdate -= DalamudUtil_FrameworkUpdate;
_ipcManager.PenumbraResourceLoadEvent -= Manager_PenumbraResourceLoadEvent;
_dalamudUtil.ClassJobChanged -= DalamudUtil_ClassJobChanged;
_ipcManager.PenumbraModSettingChanged -= Manager_PenumbraModSettingChanged;
TransientResources.Clear();
SemiTransientResources.Clear();
if (SemiTransientResources.ContainsKey(ObjectKind.Player))
{
File.WriteAllLines(PersistentDataCache, SemiTransientResources[ObjectKind.Player].SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase));
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey]
= SemiTransientResources[ObjectKind.Player].SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase).ToHashSet(StringComparer.OrdinalIgnoreCase);
_configurationService.Save();
}
}