rebuild PlayerManager to CacheCreationService and optimize creation of the local file cache
This commit is contained in:
@@ -11,6 +11,7 @@ using MareSynchronos.Utils;
|
|||||||
using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object;
|
using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
using Weapon = MareSynchronos.Interop.Weapon;
|
using Weapon = MareSynchronos.Interop.Weapon;
|
||||||
|
using MareSynchronos.FileCache;
|
||||||
|
|
||||||
namespace MareSynchronos.Factories;
|
namespace MareSynchronos.Factories;
|
||||||
|
|
||||||
@@ -19,23 +20,23 @@ public class CharacterDataFactory
|
|||||||
private readonly DalamudUtil _dalamudUtil;
|
private readonly DalamudUtil _dalamudUtil;
|
||||||
private readonly IpcManager _ipcManager;
|
private readonly IpcManager _ipcManager;
|
||||||
private readonly TransientResourceManager _transientResourceManager;
|
private readonly TransientResourceManager _transientResourceManager;
|
||||||
private readonly FileReplacementFactory _fileReplacementFactory;
|
private readonly FileCacheManager _fileCacheManager;
|
||||||
|
|
||||||
public CharacterDataFactory(DalamudUtil dalamudUtil, IpcManager ipcManager, TransientResourceManager transientResourceManager, FileReplacementFactory fileReplacementFactory)
|
public CharacterDataFactory(DalamudUtil dalamudUtil, IpcManager ipcManager, TransientResourceManager transientResourceManager, FileCacheManager fileReplacementFactory)
|
||||||
{
|
{
|
||||||
Logger.Verbose("Creating " + nameof(CharacterDataFactory));
|
Logger.Verbose("Creating " + nameof(CharacterDataFactory));
|
||||||
_dalamudUtil = dalamudUtil;
|
_dalamudUtil = dalamudUtil;
|
||||||
_ipcManager = ipcManager;
|
_ipcManager = ipcManager;
|
||||||
_transientResourceManager = transientResourceManager;
|
_transientResourceManager = transientResourceManager;
|
||||||
_fileReplacementFactory = fileReplacementFactory;
|
_fileCacheManager = fileReplacementFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe bool CheckForPointer(IntPtr playerPointer)
|
private unsafe bool CheckForNullDrawObject(IntPtr playerPointer)
|
||||||
{
|
{
|
||||||
return playerPointer == IntPtr.Zero || ((Character*)playerPointer)->GameObject.GetDrawObject() == null;
|
return ((Character*)playerPointer)->GameObject.DrawObject == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CharacterData BuildCharacterData(CharacterData previousData, PlayerRelatedObject playerRelatedObject, CancellationToken token)
|
public CharacterData BuildCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token)
|
||||||
{
|
{
|
||||||
if (!_ipcManager.Initialized)
|
if (!_ipcManager.Initialized)
|
||||||
{
|
{
|
||||||
@@ -45,7 +46,16 @@ public class CharacterDataFactory
|
|||||||
bool pointerIsZero = true;
|
bool pointerIsZero = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
pointerIsZero = CheckForPointer(playerRelatedObject.Address);
|
pointerIsZero = playerRelatedObject.Address == IntPtr.Zero;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pointerIsZero = CheckForNullDrawObject(playerRelatedObject.Address);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
pointerIsZero = true;
|
||||||
|
Logger.Debug("NullRef for " + playerRelatedObject.ObjectKind);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -67,17 +77,20 @@ public class CharacterDataFactory
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
pathsToForwardResolve.Clear();
|
||||||
|
pathsToReverseResolve.Clear();
|
||||||
return CreateCharacterData(previousData, playerRelatedObject, token);
|
return CreateCharacterData(previousData, playerRelatedObject, token);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
Logger.Debug("Cancelled creating Character data");
|
Logger.Debug("Cancelled creating Character data");
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Warn("Failed to create " + playerRelatedObject.ObjectKind + " data");
|
Logger.Debug("Failed to create " + playerRelatedObject.ObjectKind + " data");
|
||||||
Logger.Warn(e.Message);
|
Logger.Debug(e.Message);
|
||||||
Logger.Warn(e.StackTrace ?? string.Empty);
|
Logger.Debug(e.StackTrace ?? string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
previousData.FileReplacements = previousFileReplacements;
|
previousData.FileReplacements = previousFileReplacements;
|
||||||
@@ -85,23 +98,7 @@ public class CharacterDataFactory
|
|||||||
return previousData;
|
return previousData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private (string, string) GetIndentationForInheritanceLevel(int inheritanceLevel)
|
private unsafe void AddReplacementsFromRenderModel(RenderModel* mdl)
|
||||||
{
|
|
||||||
return (string.Join("", Enumerable.Repeat("\t", inheritanceLevel)), string.Join("", Enumerable.Repeat("\t", inheritanceLevel + 2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DebugPrint(FileReplacement fileReplacement, ObjectKind objectKind, string resourceType, int inheritanceLevel)
|
|
||||||
{
|
|
||||||
var indentation = GetIndentationForInheritanceLevel(inheritanceLevel);
|
|
||||||
|
|
||||||
if (fileReplacement.HasFileReplacement)
|
|
||||||
{
|
|
||||||
Logger.Verbose(indentation.Item1 + objectKind + resourceType + " [" + string.Join(", ", fileReplacement.GamePaths) + "]");
|
|
||||||
Logger.Verbose(indentation.Item2 + "=> " + fileReplacement.ResolvedPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void AddReplacementsFromRenderModel(RenderModel* mdl, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0)
|
|
||||||
{
|
{
|
||||||
if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
|
if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
|
||||||
{
|
{
|
||||||
@@ -115,26 +112,23 @@ public class CharacterDataFactory
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Logger.Warn("Could not get model data for " + objectKind);
|
Logger.Warn("Could not get model data");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Logger.Verbose("Checking File Replacement for Model " + mdlPath);
|
Logger.Verbose("Checking File Replacement for Model " + mdlPath);
|
||||||
|
|
||||||
FileReplacement mdlFileReplacement = CreateFileReplacement(mdlPath);
|
AddResolvePath(mdlPath);
|
||||||
DebugPrint(mdlFileReplacement, objectKind, "Model", inheritanceLevel);
|
|
||||||
|
|
||||||
cache.AddFileReplacement(objectKind, mdlFileReplacement);
|
|
||||||
|
|
||||||
for (var mtrlIdx = 0; mtrlIdx < mdl->MaterialCount; mtrlIdx++)
|
for (var mtrlIdx = 0; mtrlIdx < mdl->MaterialCount; mtrlIdx++)
|
||||||
{
|
{
|
||||||
var mtrl = (Material*)mdl->Materials[mtrlIdx];
|
var mtrl = (Material*)mdl->Materials[mtrlIdx];
|
||||||
if (mtrl == null) continue;
|
if (mtrl == null) continue;
|
||||||
|
|
||||||
AddReplacementsFromMaterial(mtrl, objectKind, cache, inheritanceLevel + 1);
|
AddReplacementsFromMaterial(mtrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void AddReplacementsFromMaterial(Material* mtrl, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0)
|
private unsafe void AddReplacementsFromMaterial(Material* mtrl)
|
||||||
{
|
{
|
||||||
string fileName;
|
string fileName;
|
||||||
try
|
try
|
||||||
@@ -144,25 +138,15 @@ public class CharacterDataFactory
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Logger.Warn("Could not get material data for " + objectKind);
|
Logger.Warn("Could not get material data");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Verbose("Checking File Replacement for Material " + fileName);
|
Logger.Verbose("Checking File Replacement for Material " + fileName);
|
||||||
var mtrlPath = fileName.Split("|")[2];
|
var mtrlPath = fileName.Split("|")[2];
|
||||||
|
|
||||||
if (cache.FileReplacements.ContainsKey(objectKind))
|
|
||||||
{
|
|
||||||
if (cache.FileReplacements[objectKind].Any(c => c.ResolvedPath.Contains(mtrlPath, StringComparison.Ordinal)))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var mtrlFileReplacement = CreateFileReplacement(mtrlPath);
|
AddResolvePath(mtrlPath);
|
||||||
DebugPrint(mtrlFileReplacement, objectKind, "Material", inheritanceLevel);
|
|
||||||
|
|
||||||
cache.AddFileReplacement(objectKind, mtrlFileReplacement);
|
|
||||||
|
|
||||||
var mtrlResourceHandle = (MtrlResource*)mtrl->ResourceHandle;
|
var mtrlResourceHandle = (MtrlResource*)mtrl->ResourceHandle;
|
||||||
for (var resIdx = 0; resIdx < mtrlResourceHandle->NumTex; resIdx++)
|
for (var resIdx = 0; resIdx < mtrlResourceHandle->NumTex; resIdx++)
|
||||||
@@ -181,14 +165,14 @@ public class CharacterDataFactory
|
|||||||
|
|
||||||
Logger.Verbose("Checking File Replacement for Texture " + texPath);
|
Logger.Verbose("Checking File Replacement for Texture " + texPath);
|
||||||
|
|
||||||
AddReplacementsFromTexture(texPath, objectKind, cache, inheritanceLevel + 1);
|
AddReplacementsFromTexture(texPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var shpkPath = "shader/sm5/shpk/" + new ByteString(mtrlResourceHandle->ShpkString).ToString();
|
var shpkPath = "shader/sm5/shpk/" + new ByteString(mtrlResourceHandle->ShpkString).ToString();
|
||||||
Logger.Verbose("Checking File Replacement for Shader " + shpkPath);
|
Logger.Verbose("Checking File Replacement for Shader " + shpkPath);
|
||||||
AddReplacementsFromShader(shpkPath, objectKind, cache, inheritanceLevel + 1);
|
AddReplacementsFromShader(shpkPath);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -196,130 +180,92 @@ public class CharacterDataFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddReplacement(string varPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0, bool doNotReverseResolve = false)
|
private void AddReplacement(string varPath, bool doNotReverseResolve = false)
|
||||||
{
|
{
|
||||||
if (varPath.IsNullOrEmpty()) return;
|
if (varPath.IsNullOrEmpty()) return;
|
||||||
|
|
||||||
if (cache.FileReplacements.ContainsKey(objectKind))
|
AddResolvePath(varPath, doNotReverseResolve);
|
||||||
{
|
|
||||||
if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(varPath, StringComparer.Ordinal)))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var variousReplacement = CreateFileReplacement(varPath, doNotReverseResolve);
|
|
||||||
DebugPrint(variousReplacement, objectKind, "Various", inheritanceLevel);
|
|
||||||
|
|
||||||
cache.AddFileReplacement(objectKind, variousReplacement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddReplacementsFromShader(string shpkPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0)
|
private void AddReplacementsFromShader(string shpkPath)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(shpkPath)) return;
|
if (string.IsNullOrEmpty(shpkPath)) return;
|
||||||
|
|
||||||
if (cache.FileReplacements.ContainsKey(objectKind))
|
AddResolvePath(shpkPath, doNotReverseResolve: true);
|
||||||
{
|
|
||||||
if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(shpkPath, StringComparer.Ordinal)))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var shpkFileReplacement = CreateFileReplacement(shpkPath, doNotReverseResolve: true);
|
|
||||||
DebugPrint(shpkFileReplacement, objectKind, "Shader", inheritanceLevel);
|
|
||||||
cache.AddFileReplacement(objectKind, shpkFileReplacement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddReplacementsFromTexture(string texPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0, bool doNotReverseResolve = true)
|
private void AddReplacementsFromTexture(string texPath, bool doNotReverseResolve = true)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(texPath)) return;
|
if (string.IsNullOrEmpty(texPath)) return;
|
||||||
|
|
||||||
if (cache.FileReplacements.ContainsKey(objectKind))
|
AddResolvePath(texPath, doNotReverseResolve);
|
||||||
{
|
|
||||||
if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(texPath, StringComparer.Ordinal)))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var texFileReplacement = CreateFileReplacement(texPath, doNotReverseResolve);
|
|
||||||
DebugPrint(texFileReplacement, objectKind, "Texture", inheritanceLevel);
|
|
||||||
|
|
||||||
cache.AddFileReplacement(objectKind, texFileReplacement);
|
|
||||||
|
|
||||||
if (texPath.Contains("/--", StringComparison.Ordinal)) return;
|
if (texPath.Contains("/--", StringComparison.Ordinal)) return;
|
||||||
|
|
||||||
var texDx11Replacement =
|
AddResolvePath(texPath.Insert(texPath.LastIndexOf('/') + 1, "--"), doNotReverseResolve);
|
||||||
CreateFileReplacement(texPath.Insert(texPath.LastIndexOf('/') + 1, "--"), doNotReverseResolve);
|
|
||||||
|
|
||||||
DebugPrint(texDx11Replacement, objectKind, "Texture (DX11)", inheritanceLevel);
|
|
||||||
|
|
||||||
cache.AddFileReplacement(objectKind, texDx11Replacement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe CharacterData CreateCharacterData(CharacterData previousData, PlayerRelatedObject playerRelatedObject, CancellationToken token)
|
private unsafe CharacterData CreateCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token)
|
||||||
{
|
{
|
||||||
var objectKind = playerRelatedObject.ObjectKind;
|
var objectKind = playerRelatedObject.ObjectKind;
|
||||||
var charaPointer = playerRelatedObject.Address;
|
var charaPointer = playerRelatedObject.Address;
|
||||||
|
|
||||||
|
if (!previousData.FileReplacements.ContainsKey(objectKind))
|
||||||
|
{
|
||||||
|
previousData.FileReplacements[objectKind] = new(FileReplacementComparer.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
_dalamudUtil.WaitWhileCharacterIsDrawing(playerRelatedObject.ObjectKind.ToString(), playerRelatedObject.Address, ct: token);
|
||||||
|
|
||||||
Stopwatch st = Stopwatch.StartNew();
|
Stopwatch st = Stopwatch.StartNew();
|
||||||
|
|
||||||
if (playerRelatedObject.HasUnprocessedUpdate)
|
Logger.Debug("Handling unprocessed update for " + objectKind);
|
||||||
|
|
||||||
|
if (previousData.FileReplacements.ContainsKey(objectKind))
|
||||||
{
|
{
|
||||||
Logger.Debug("Handling unprocessed update for " + objectKind);
|
previousData.FileReplacements[objectKind].Clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (previousData.FileReplacements.ContainsKey(objectKind))
|
var chara = _dalamudUtil.CreateGameObject(charaPointer)!;
|
||||||
|
while (!DalamudUtil.IsObjectPresent(chara))
|
||||||
|
{
|
||||||
|
Logger.Verbose("Character is null but it shouldn't be, waiting");
|
||||||
|
Thread.Sleep(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
var human = (Human*)((Character*)charaPointer)->GameObject.DrawObject;
|
||||||
|
for (var mdlIdx = 0; mdlIdx < human->CharacterBase.SlotCount; ++mdlIdx)
|
||||||
|
{
|
||||||
|
var mdl = (RenderModel*)human->CharacterBase.ModelArray[mdlIdx];
|
||||||
|
if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
|
||||||
{
|
{
|
||||||
previousData.FileReplacements[objectKind].Clear();
|
continue;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
previousData.FileReplacements.Add(objectKind, new());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var chara = _dalamudUtil.CreateGameObject(charaPointer)!;
|
token.ThrowIfCancellationRequested();
|
||||||
while (!DalamudUtil.IsObjectPresent(chara))
|
|
||||||
{
|
|
||||||
Logger.Verbose("Character is null but it shouldn't be, waiting");
|
|
||||||
Thread.Sleep(50);
|
|
||||||
}
|
|
||||||
|
|
||||||
var human = (Human*)((Character*)charaPointer)->GameObject.GetDrawObject();
|
AddReplacementsFromRenderModel(mdl);
|
||||||
for (var mdlIdx = 0; mdlIdx < human->CharacterBase.SlotCount; ++mdlIdx)
|
}
|
||||||
{
|
|
||||||
var mdl = (RenderModel*)human->CharacterBase.ModelArray[mdlIdx];
|
|
||||||
if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
token.ThrowIfCancellationRequested();
|
if (objectKind == ObjectKind.Player)
|
||||||
|
{
|
||||||
AddReplacementsFromRenderModel(mdl, objectKind, previousData, 0);
|
AddPlayerSpecificReplacements(objectKind, charaPointer, human);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (objectKind == ObjectKind.Pet)
|
||||||
|
{
|
||||||
foreach (var item in previousData.FileReplacements[objectKind])
|
foreach (var item in previousData.FileReplacements[objectKind])
|
||||||
{
|
{
|
||||||
_transientResourceManager.RemoveTransientResource(charaPointer, item);
|
_transientResourceManager.AddSemiTransientResource(objectKind, item.GamePaths.First());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (objectKind == ObjectKind.Player)
|
previousData.FileReplacements[objectKind].Clear();
|
||||||
{
|
|
||||||
AddPlayerSpecificReplacements(previousData, objectKind, charaPointer, human);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (objectKind == ObjectKind.Pet)
|
|
||||||
{
|
|
||||||
foreach (var item in previousData.FileReplacements[objectKind])
|
|
||||||
{
|
|
||||||
_transientResourceManager.AddSemiTransientResource(objectKind, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
previousData.FileReplacements[objectKind].Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Dictionary<string, List<string>> resolvedPaths = GetFileReplacementsFromPaths();
|
||||||
|
previousData.FileReplacements[objectKind] = new HashSet<FileReplacement>(resolvedPaths.Select(c => new FileReplacement(c.Value, c.Key, _fileCacheManager)));
|
||||||
|
|
||||||
previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations();
|
previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations();
|
||||||
previousData.GlamourerString[objectKind] = _ipcManager.GlamourerGetCharacterCustomization(charaPointer);
|
previousData.GlamourerString[objectKind] = _ipcManager.GlamourerGetCharacterCustomization(charaPointer);
|
||||||
previousData.HeelsOffset = _ipcManager.GetHeelsOffset();
|
previousData.HeelsOffset = _ipcManager.GetHeelsOffset();
|
||||||
@@ -327,52 +273,75 @@ public class CharacterDataFactory
|
|||||||
previousData.PalettePlusPalette = _ipcManager.PalettePlusBuildPalette();
|
previousData.PalettePlusPalette = _ipcManager.PalettePlusBuildPalette();
|
||||||
|
|
||||||
Logger.Debug("Handling transient update for " + objectKind);
|
Logger.Debug("Handling transient update for " + objectKind);
|
||||||
ManageSemiTransientData(previousData, objectKind, charaPointer);
|
_transientResourceManager.ClearTransientPaths(charaPointer, previousData.FileReplacements[objectKind].SelectMany(c => c.GamePaths).ToList());
|
||||||
|
|
||||||
|
pathsToForwardResolve.Clear();
|
||||||
|
pathsToReverseResolve.Clear();
|
||||||
|
|
||||||
|
ManageSemiTransientData(objectKind, charaPointer);
|
||||||
|
|
||||||
|
var resolvedTransientPaths = GetFileReplacementsFromPaths();
|
||||||
|
foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement(c.Value, c.Key, _fileCacheManager)))
|
||||||
|
{
|
||||||
|
previousData.FileReplacements[objectKind].Add(replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in previousData.FileReplacements[objectKind])
|
||||||
|
{
|
||||||
|
Logger.Debug(item.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
st.Stop();
|
st.Stop();
|
||||||
Logger.Verbose("Building " + objectKind + " Data took " + st.Elapsed);
|
Logger.Verbose("Building " + objectKind + " Data took " + st.ElapsedMilliseconds + "ms");
|
||||||
return previousData;
|
return previousData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void ManageSemiTransientData(CharacterData previousData, ObjectKind objectKind, IntPtr charaPointer)
|
private Dictionary<string, List<string>> GetFileReplacementsFromPaths()
|
||||||
{
|
{
|
||||||
_transientResourceManager.PersistTransientResources(charaPointer, objectKind, CreateFileReplacement);
|
var forwardPaths = pathsToForwardResolve.ToArray();
|
||||||
|
var reversePaths = pathsToReverseResolve.ToArray();
|
||||||
// get rid of items that have no file replacements anymore
|
Dictionary<string, List<string>> resolvedPaths = new(StringComparer.Ordinal);
|
||||||
foreach (var entry in previousData.FileReplacements.ToList())
|
var result = _ipcManager.PenumbraResolvePaths(pathsToForwardResolve.ToArray(), pathsToReverseResolve.ToArray());
|
||||||
|
for (int i = 0; i < forwardPaths.Length; i++)
|
||||||
{
|
{
|
||||||
foreach (var item in entry.Value.ToList())
|
var filePath = result.forward[i].ToLowerInvariant();
|
||||||
|
if (resolvedPaths.TryGetValue(filePath, out var list))
|
||||||
{
|
{
|
||||||
if (!item.HasFileReplacement) previousData.FileReplacements[entry.Key].Remove(item);
|
list.Add(forwardPaths[i].ToLowerInvariant());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resolvedPaths[filePath] = new List<string> { forwardPaths[i].ToLowerInvariant() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < reversePaths.Length; i++)
|
||||||
|
{
|
||||||
|
var filePath = reversePaths[i].ToLowerInvariant();
|
||||||
|
if (resolvedPaths.TryGetValue(filePath, out var list))
|
||||||
|
{
|
||||||
|
list.AddRange(result.reverse[i].Select(c => c.ToLowerInvariant()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resolvedPaths[filePath] = new List<string>(result.reverse[i].Select(c => c.ToLowerInvariant()).ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void ManageSemiTransientData(ObjectKind objectKind, IntPtr charaPointer)
|
||||||
|
{
|
||||||
|
_transientResourceManager.PersistTransientResources(charaPointer, objectKind);
|
||||||
|
|
||||||
foreach (var item in _transientResourceManager.GetSemiTransientResources(objectKind))
|
foreach (var item in _transientResourceManager.GetSemiTransientResources(objectKind))
|
||||||
{
|
{
|
||||||
if (!previousData.FileReplacements.ContainsKey(objectKind))
|
AddResolvePath(item, true);
|
||||||
{
|
|
||||||
previousData.FileReplacements.Add(objectKind, new());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!previousData.FileReplacements[objectKind].Any(k => k.GamePaths.Any(p => item.GamePaths.Contains(p, StringComparer.OrdinalIgnoreCase))))
|
|
||||||
{
|
|
||||||
var penumResolve = _ipcManager.PenumbraResolvePath(item.GamePaths.First()).ToLowerInvariant();
|
|
||||||
var gamePath = item.GamePaths.First().ToLowerInvariant();
|
|
||||||
if (string.Equals(penumResolve, gamePath, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
Logger.Verbose("PenumResolve was same as GamePath, not adding " + item);
|
|
||||||
_transientResourceManager.RemoveTransientResource(charaPointer, item);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Verbose("Found semi transient resource: " + item);
|
|
||||||
previousData.FileReplacements[objectKind].Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void AddPlayerSpecificReplacements(CharacterData previousData, ObjectKind objectKind, IntPtr charaPointer, Human* human)
|
private unsafe void AddPlayerSpecificReplacements(ObjectKind objectKind, IntPtr charaPointer, Human* human)
|
||||||
{
|
{
|
||||||
var weaponObject = (Weapon*)((Object*)human)->ChildObject;
|
var weaponObject = (Weapon*)((Object*)human)->ChildObject;
|
||||||
|
|
||||||
@@ -380,42 +349,32 @@ public class CharacterDataFactory
|
|||||||
{
|
{
|
||||||
var mainHandWeapon = weaponObject->WeaponRenderModel->RenderModel;
|
var mainHandWeapon = weaponObject->WeaponRenderModel->RenderModel;
|
||||||
|
|
||||||
AddReplacementsFromRenderModel(mainHandWeapon, objectKind, previousData, 0);
|
AddReplacementsFromRenderModel(mainHandWeapon);
|
||||||
|
|
||||||
foreach (var item in previousData.FileReplacements[objectKind])
|
|
||||||
{
|
|
||||||
_transientResourceManager.RemoveTransientResource(charaPointer, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in _transientResourceManager.GetTransientResources((IntPtr)weaponObject))
|
foreach (var item in _transientResourceManager.GetTransientResources((IntPtr)weaponObject))
|
||||||
{
|
{
|
||||||
Logger.Verbose("Found transient weapon resource: " + item);
|
Logger.Verbose("Found transient weapon resource: " + item);
|
||||||
AddReplacement(item, objectKind, previousData, 1, doNotReverseResolve: true);
|
AddReplacement(item, doNotReverseResolve: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weaponObject->NextSibling != (IntPtr)weaponObject)
|
if (weaponObject->NextSibling != (IntPtr)weaponObject)
|
||||||
{
|
{
|
||||||
var offHandWeapon = ((Weapon*)weaponObject->NextSibling)->WeaponRenderModel->RenderModel;
|
var offHandWeapon = ((Weapon*)weaponObject->NextSibling)->WeaponRenderModel->RenderModel;
|
||||||
|
|
||||||
AddReplacementsFromRenderModel(offHandWeapon, objectKind, previousData, 1);
|
AddReplacementsFromRenderModel(offHandWeapon);
|
||||||
|
|
||||||
foreach (var item in previousData.FileReplacements[objectKind])
|
|
||||||
{
|
|
||||||
_transientResourceManager.RemoveTransientResource((IntPtr)offHandWeapon, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in _transientResourceManager.GetTransientResources((IntPtr)offHandWeapon))
|
foreach (var item in _transientResourceManager.GetTransientResources((IntPtr)offHandWeapon))
|
||||||
{
|
{
|
||||||
Logger.Verbose("Found transient offhand weapon resource: " + item);
|
Logger.Verbose("Found transient offhand weapon resource: " + item);
|
||||||
AddReplacement(item, objectKind, previousData, 1, doNotReverseResolve: true);
|
AddReplacement(item, doNotReverseResolve: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AddReplacementSkeleton(((HumanExt*)human)->Human.RaceSexId, objectKind, previousData);
|
AddReplacementSkeleton(((HumanExt*)human)->Human.RaceSexId);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
AddReplacementsFromTexture(new ByteString(((HumanExt*)human)->Decal->FileName()).ToString(), objectKind, previousData, 0, doNotReverseResolve: false);
|
AddReplacementsFromTexture(new ByteString(((HumanExt*)human)->Decal->FileName()).ToString(), doNotReverseResolve: false);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -423,43 +382,29 @@ public class CharacterDataFactory
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
AddReplacementsFromTexture(new ByteString(((HumanExt*)human)->LegacyBodyDecal->FileName()).ToString(), objectKind, previousData, 0, doNotReverseResolve: false);
|
AddReplacementsFromTexture(new ByteString(((HumanExt*)human)->LegacyBodyDecal->FileName()).ToString(), doNotReverseResolve: false);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Logger.Warn("Could not get Legacy Body Decal Data");
|
Logger.Warn("Could not get Legacy Body Decal Data");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in previousData.FileReplacements[objectKind])
|
|
||||||
{
|
|
||||||
_transientResourceManager.RemoveTransientResource(charaPointer, item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddReplacementSkeleton(ushort raceSexId, ObjectKind objectKind, CharacterData cache)
|
private void AddReplacementSkeleton(ushort raceSexId)
|
||||||
{
|
{
|
||||||
string raceSexIdString = raceSexId.ToString("0000");
|
string raceSexIdString = raceSexId.ToString("0000");
|
||||||
|
|
||||||
string skeletonPath = $"chara/human/c{raceSexIdString}/skeleton/base/b0001/skl_c{raceSexIdString}b0001.sklb";
|
string skeletonPath = $"chara/human/c{raceSexIdString}/skeleton/base/b0001/skl_c{raceSexIdString}b0001.sklb";
|
||||||
|
|
||||||
var replacement = CreateFileReplacement(skeletonPath, doNotReverseResolve: true);
|
AddResolvePath(skeletonPath, doNotReverseResolve: true);
|
||||||
cache.AddFileReplacement(objectKind, replacement);
|
|
||||||
|
|
||||||
DebugPrint(replacement, objectKind, "SKLB", 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileReplacement CreateFileReplacement(string path, bool doNotReverseResolve = false)
|
private void AddResolvePath(string path, bool doNotReverseResolve = false)
|
||||||
{
|
{
|
||||||
var fileReplacement = _fileReplacementFactory.Create();
|
if (doNotReverseResolve) pathsToForwardResolve.Add(path.ToLowerInvariant());
|
||||||
if (!doNotReverseResolve)
|
else pathsToReverseResolve.Add(path.ToLowerInvariant());
|
||||||
{
|
|
||||||
fileReplacement.ReverseResolvePath(path);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fileReplacement.ResolvePath(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileReplacement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HashSet<string> pathsToForwardResolve = new(StringComparer.Ordinal);
|
||||||
|
private HashSet<string> pathsToReverseResolve = new(StringComparer.Ordinal);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
using MareSynchronos.FileCache;
|
|
||||||
using MareSynchronos.Managers;
|
|
||||||
using MareSynchronos.Models;
|
|
||||||
|
|
||||||
namespace MareSynchronos.Factories;
|
|
||||||
|
|
||||||
public class FileReplacementFactory
|
|
||||||
{
|
|
||||||
private readonly FileCacheManager fileCacheManager;
|
|
||||||
private readonly IpcManager ipcManager;
|
|
||||||
|
|
||||||
public FileReplacementFactory(FileCacheManager fileCacheManager, IpcManager ipcManager)
|
|
||||||
{
|
|
||||||
this.fileCacheManager = fileCacheManager;
|
|
||||||
this.ipcManager = ipcManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileReplacement Create()
|
|
||||||
{
|
|
||||||
return new FileReplacement(fileCacheManager, ipcManager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -109,8 +109,8 @@ public class FileCacheManager : IDisposable
|
|||||||
|
|
||||||
public FileCacheEntity? GetFileCacheByPath(string path)
|
public FileCacheEntity? GetFileCacheByPath(string path)
|
||||||
{
|
{
|
||||||
var cleanedPath = path.Replace("/", "\\", StringComparison.OrdinalIgnoreCase).ToLowerInvariant().Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), "", StringComparison.OrdinalIgnoreCase);
|
var cleanedPath = path.Replace("/", "\\", StringComparison.OrdinalIgnoreCase).ToLowerInvariant().Replace(_ipcManager.PenumbraModDirectory!.ToLowerInvariant(), "", StringComparison.OrdinalIgnoreCase);
|
||||||
var entry = _fileCaches.FirstOrDefault(f => f.Value.ResolvedFilepath.EndsWith(cleanedPath, StringComparison.OrdinalIgnoreCase)).Value;
|
var entry = _fileCaches.Values.FirstOrDefault(f => f.ResolvedFilepath.EndsWith(cleanedPath, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
if (entry == null)
|
if (entry == null)
|
||||||
{
|
{
|
||||||
@@ -140,8 +140,8 @@ public class FileCacheManager : IDisposable
|
|||||||
FileInfo fi = new(path);
|
FileInfo fi = new(path);
|
||||||
if (!fi.Exists) return null;
|
if (!fi.Exists) return null;
|
||||||
var fullName = fi.FullName.ToLowerInvariant();
|
var fullName = fi.FullName.ToLowerInvariant();
|
||||||
if (!fullName.Contains(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), StringComparison.Ordinal)) return null;
|
if (!fullName.Contains(_ipcManager.PenumbraModDirectory!.ToLowerInvariant(), StringComparison.Ordinal)) return null;
|
||||||
string prefixedPath = fullName.Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), _penumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal);
|
string prefixedPath = fullName.Replace(_ipcManager.PenumbraModDirectory!.ToLowerInvariant(), _penumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal);
|
||||||
return CreateFileCacheEntity(fi, prefixedPath);
|
return CreateFileCacheEntity(fi, prefixedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ public class FileCacheManager : IDisposable
|
|||||||
{
|
{
|
||||||
if (fileCache.PrefixedFilePath.StartsWith(_penumbraPrefix, StringComparison.OrdinalIgnoreCase))
|
if (fileCache.PrefixedFilePath.StartsWith(_penumbraPrefix, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(_penumbraPrefix, _ipcManager.PenumbraModDirectory(), StringComparison.Ordinal));
|
fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(_penumbraPrefix, _ipcManager.PenumbraModDirectory, StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
else if (fileCache.PrefixedFilePath.StartsWith(_cachePrefix, StringComparison.OrdinalIgnoreCase))
|
else if (fileCache.PrefixedFilePath.StartsWith(_cachePrefix, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ public class PeriodicFileScanner : MediatorSubscriberBase, IDisposable
|
|||||||
private void PeriodicFileScan(CancellationToken ct)
|
private void PeriodicFileScan(CancellationToken ct)
|
||||||
{
|
{
|
||||||
TotalFiles = 1;
|
TotalFiles = 1;
|
||||||
var penumbraDir = _ipcManager.PenumbraModDirectory();
|
var penumbraDir = _ipcManager.PenumbraModDirectory;
|
||||||
bool penDirExists = true;
|
bool penDirExists = true;
|
||||||
bool cacheDirExists = true;
|
bool cacheDirExists = true;
|
||||||
if (string.IsNullOrEmpty(penumbraDir) || !Directory.Exists(penumbraDir))
|
if (string.IsNullOrEmpty(penumbraDir) || !Directory.Exists(penumbraDir))
|
||||||
|
|||||||
114
MareSynchronos/Managers/CacheCreationService.cs
Normal file
114
MareSynchronos/Managers/CacheCreationService.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
|
using MareSynchronos.API.Data.Enum;
|
||||||
|
using MareSynchronos.Factories;
|
||||||
|
using MareSynchronos.Mediator;
|
||||||
|
using MareSynchronos.Models;
|
||||||
|
using MareSynchronos.Utils;
|
||||||
|
using MareSynchronos.WebAPI;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Managers;
|
||||||
|
|
||||||
|
public class CacheCreationService : MediatorSubscriberBase, IDisposable
|
||||||
|
{
|
||||||
|
private readonly CharacterDataFactory _characterDataFactory;
|
||||||
|
private readonly IpcManager _ipcManager;
|
||||||
|
private readonly ApiController _apiController;
|
||||||
|
private Task? _cacheCreationTask;
|
||||||
|
private Dictionary<ObjectKind, GameObjectHandler> _cachesToCreate = new();
|
||||||
|
private CharacterData _lastCreatedData = new();
|
||||||
|
private CancellationTokenSource cts = new();
|
||||||
|
private List<GameObjectHandler> _playerRelatedObjects;
|
||||||
|
|
||||||
|
public unsafe CacheCreationService(MareMediator mediator, CharacterDataFactory characterDataFactory, IpcManager ipcManager,
|
||||||
|
ApiController apiController, DalamudUtil dalamudUtil) : base(mediator)
|
||||||
|
{
|
||||||
|
_characterDataFactory = characterDataFactory;
|
||||||
|
_ipcManager = ipcManager;
|
||||||
|
_apiController = apiController;
|
||||||
|
|
||||||
|
Mediator.Subscribe<CreateCacheForObjectMessage>(this, (msg) =>
|
||||||
|
{
|
||||||
|
var actualMsg = (CreateCacheForObjectMessage)msg;
|
||||||
|
_cachesToCreate[actualMsg.ObjectToCreateFor.ObjectKind] = actualMsg.ObjectToCreateFor;
|
||||||
|
});
|
||||||
|
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (msg) => ProcessCacheCreation());
|
||||||
|
Mediator.Subscribe<CustomizePlusMessage>(this, (msg) => CustomizePlusChanged((CustomizePlusMessage)msg));
|
||||||
|
Mediator.Subscribe<HeelsOffsetMessage>(this, (msg) => HeelsOffsetChanged((HeelsOffsetMessage)msg));
|
||||||
|
Mediator.Subscribe<PalettePlusMessage>(this, (msg) => PalettePlusChanged((PalettePlusMessage)msg));
|
||||||
|
|
||||||
|
_playerRelatedObjects = new List<GameObjectHandler>()
|
||||||
|
{
|
||||||
|
new(Mediator, ObjectKind.Player, () => dalamudUtil.PlayerPointer),
|
||||||
|
new(Mediator, ObjectKind.MinionOrMount, () => (IntPtr)((Character*)dalamudUtil.PlayerPointer)->CompanionObject),
|
||||||
|
new(Mediator, ObjectKind.Pet, () => dalamudUtil.GetPet()),
|
||||||
|
new(Mediator, ObjectKind.Companion, () => dalamudUtil.GetCompanion()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PalettePlusChanged(PalettePlusMessage msg)
|
||||||
|
{
|
||||||
|
if (!string.Equals(msg.Data, _lastCreatedData.PalettePlusPalette, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_lastCreatedData.PalettePlusPalette = msg.Data ?? string.Empty;
|
||||||
|
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HeelsOffsetChanged(HeelsOffsetMessage msg)
|
||||||
|
{
|
||||||
|
if (msg.Offset != _lastCreatedData.HeelsOffset)
|
||||||
|
{
|
||||||
|
_lastCreatedData.HeelsOffset = msg.Offset;
|
||||||
|
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CustomizePlusChanged(CustomizePlusMessage msg)
|
||||||
|
{
|
||||||
|
if (!string.Equals(msg.Data, _lastCreatedData.CustomizePlusScale, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_lastCreatedData.CustomizePlusScale = msg.Data ?? string.Empty;
|
||||||
|
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessCacheCreation()
|
||||||
|
{
|
||||||
|
if (_cachesToCreate.Any() && (_cacheCreationTask?.IsCompleted ?? true))
|
||||||
|
{
|
||||||
|
var toCreate = _cachesToCreate.ToList();
|
||||||
|
_cachesToCreate.Clear();
|
||||||
|
_cacheCreationTask = Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var obj in toCreate)
|
||||||
|
{
|
||||||
|
var data = _characterDataFactory.BuildCharacterData(_lastCreatedData, obj.Value, cts.Token);
|
||||||
|
}
|
||||||
|
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error("Error during Cache Creation Processing", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Logger.Debug("Cache Creation complete");
|
||||||
|
|
||||||
|
}
|
||||||
|
}, cts.Token);
|
||||||
|
}
|
||||||
|
else if (_cachesToCreate.Any())
|
||||||
|
{
|
||||||
|
Logger.Debug("Cache Creation stored until previous creation finished");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
base.Dispose();
|
||||||
|
_playerRelatedObjects.ForEach(p => p.Dispose());
|
||||||
|
cts.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
|||||||
private readonly IpcManager _ipcManager;
|
private readonly IpcManager _ipcManager;
|
||||||
private readonly FileCacheManager _fileDbManager;
|
private readonly FileCacheManager _fileDbManager;
|
||||||
private API.Data.CharacterData _cachedData = new();
|
private API.Data.CharacterData _cachedData = new();
|
||||||
private PlayerRelatedObject? _currentCharacterEquipment;
|
private GameObjectHandler? _currentCharacterEquipment;
|
||||||
private CancellationTokenSource? _downloadCancellationTokenSource = new();
|
private CancellationTokenSource? _downloadCancellationTokenSource = new();
|
||||||
private bool _isVisible;
|
private bool _isVisible;
|
||||||
|
|
||||||
@@ -65,6 +65,18 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
|||||||
Logger.Debug("Checking for files to download for player " + PlayerName);
|
Logger.Debug("Checking for files to download for player " + PlayerName);
|
||||||
Logger.Debug("Hash for data is " + characterData.DataHash.Value + ", current cache hash is " + _cachedData.DataHash.Value);
|
Logger.Debug("Hash for data is " + characterData.DataHash.Value + ", current cache hash is " + _cachedData.DataHash.Value);
|
||||||
|
|
||||||
|
if (!_ipcManager.CheckPenumbraApi())
|
||||||
|
{
|
||||||
|
Mediator.Publish(new NotificationMessage("Penumbra inactive", "Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use Mare.", NotificationType.Error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_ipcManager.CheckGlamourerApi())
|
||||||
|
{
|
||||||
|
Mediator.Publish(new NotificationMessage("Glamourer inactive", "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Mare.", NotificationType.Error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.Equals(characterData.DataHash.Value, _cachedData.DataHash.Value, StringComparison.Ordinal) && !forced) return;
|
if (string.Equals(characterData.DataHash.Value, _cachedData.DataHash.Value, StringComparison.Ordinal) && !forced) return;
|
||||||
|
|
||||||
bool updateModdedPaths = false;
|
bool updateModdedPaths = false;
|
||||||
@@ -116,6 +128,14 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
|||||||
|
|
||||||
if (objectKind == ObjectKind.Player)
|
if (objectKind == ObjectKind.Player)
|
||||||
{
|
{
|
||||||
|
bool manipDataDifferent = !string.Equals(_cachedData.ManipulationData, characterData.ManipulationData, StringComparison.Ordinal);
|
||||||
|
if (manipDataDifferent)
|
||||||
|
{
|
||||||
|
Logger.Debug("Updating " + objectKind);
|
||||||
|
charaDataToUpdate.Add(objectKind);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
bool heelsOffsetDifferent = _cachedData.HeelsOffset != characterData.HeelsOffset;
|
bool heelsOffsetDifferent = _cachedData.HeelsOffset != characterData.HeelsOffset;
|
||||||
if (heelsOffsetDifferent)
|
if (heelsOffsetDifferent)
|
||||||
{
|
{
|
||||||
@@ -189,8 +209,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentCharacterEquipment?.CheckAndUpdateObject();
|
if (_currentCharacterEquipment?.CheckAndUpdateObject() ?? false)
|
||||||
if (_currentCharacterEquipment?.HasUnprocessedUpdate ?? false)
|
|
||||||
{
|
{
|
||||||
OnPlayerChanged();
|
OnPlayerChanged();
|
||||||
}
|
}
|
||||||
@@ -247,8 +266,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
|||||||
|
|
||||||
Mediator.Subscribe<PenumbraRedrawMessage>(this, (msg) => IpcManagerOnPenumbraRedrawEvent(((PenumbraRedrawMessage)msg)));
|
Mediator.Subscribe<PenumbraRedrawMessage>(this, (msg) => IpcManagerOnPenumbraRedrawEvent(((PenumbraRedrawMessage)msg)));
|
||||||
_originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
|
_originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
|
||||||
_currentCharacterEquipment = new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero,
|
_currentCharacterEquipment = new GameObjectHandler(Mediator, ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName)?.Address ?? IntPtr.Zero, false);
|
||||||
() => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName)?.Address ?? IntPtr.Zero);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@@ -437,7 +455,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
|||||||
{
|
{
|
||||||
PlayerCharacter = msg.Address;
|
PlayerCharacter = msg.Address;
|
||||||
var cts = new CancellationTokenSource();
|
var cts = new CancellationTokenSource();
|
||||||
cts.CancelAfter(TimeSpan.FromSeconds(5));
|
cts.CancelAfter(TimeSpan.FromSeconds(10));
|
||||||
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 10000, cts.Token);
|
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 10000, cts.Token);
|
||||||
cts.Dispose();
|
cts.Dispose();
|
||||||
cts = new CancellationTokenSource();
|
cts = new CancellationTokenSource();
|
||||||
@@ -460,7 +478,6 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
|||||||
private void OnPlayerChanged()
|
private void OnPlayerChanged()
|
||||||
{
|
{
|
||||||
Logger.Debug($"Player {PlayerName} changed, PenumbraRedraw is {RequestedPenumbraRedraw}");
|
Logger.Debug($"Player {PlayerName} changed, PenumbraRedraw is {RequestedPenumbraRedraw}");
|
||||||
_currentCharacterEquipment!.HasUnprocessedUpdate = false;
|
|
||||||
if (!RequestedPenumbraRedraw && PlayerCharacter != IntPtr.Zero)
|
if (!RequestedPenumbraRedraw && PlayerCharacter != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
Logger.Debug($"Saving new Glamourer data");
|
Logger.Debug($"Saving new Glamourer data");
|
||||||
|
|||||||
@@ -35,8 +35,9 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
|||||||
private readonly FuncSubscriber<string, string> _penumbraResolvePlayer;
|
private readonly FuncSubscriber<string, string> _penumbraResolvePlayer;
|
||||||
private readonly FuncSubscriber<string, string[]> _reverseResolvePlayer;
|
private readonly FuncSubscriber<string, string[]> _reverseResolvePlayer;
|
||||||
private readonly FuncSubscriber<string, string, Dictionary<string, string>, string, int, PenumbraApiEc> _penumbraAddTemporaryMod;
|
private readonly FuncSubscriber<string, string, Dictionary<string, string>, string, int, PenumbraApiEc> _penumbraAddTemporaryMod;
|
||||||
|
private readonly FuncSubscriber<string[], string[], (string[], string[][])> _penumbraResolvePaths;
|
||||||
|
private readonly FuncSubscriber<bool> _penumbraEnabled;
|
||||||
private readonly EventSubscriber<nint, string, string> _penumbraGameObjectResourcePathResolved;
|
private readonly EventSubscriber<nint, string, string> _penumbraGameObjectResourcePathResolved;
|
||||||
private readonly EventSubscriber<ModSettingChange, string, string, bool> _penumbraModSettingChanged;
|
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<string> _heelsGetApiVersion;
|
private readonly ICallGateSubscriber<string> _heelsGetApiVersion;
|
||||||
private readonly ICallGateSubscriber<float> _heelsGetOffset;
|
private readonly ICallGateSubscriber<float> _heelsGetOffset;
|
||||||
@@ -49,7 +50,7 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
|||||||
private readonly ICallGateSubscriber<string, Character?, object> _customizePlusSetBodyScaleToCharacter;
|
private readonly ICallGateSubscriber<string, Character?, object> _customizePlusSetBodyScaleToCharacter;
|
||||||
private readonly ICallGateSubscriber<Character?, object> _customizePlusRevert;
|
private readonly ICallGateSubscriber<Character?, object> _customizePlusRevert;
|
||||||
private readonly ICallGateSubscriber<string?, object> _customizePlusOnScaleUpdate;
|
private readonly ICallGateSubscriber<string?, object> _customizePlusOnScaleUpdate;
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<string> _palettePlusApiVersion;
|
private readonly ICallGateSubscriber<string> _palettePlusApiVersion;
|
||||||
private readonly ICallGateSubscriber<Character, string> _palettePlusBuildCharaPalette;
|
private readonly ICallGateSubscriber<Character, string> _palettePlusBuildCharaPalette;
|
||||||
private readonly ICallGateSubscriber<Character, string, object> _palettePlusSetCharaPalette;
|
private readonly ICallGateSubscriber<Character, string, object> _palettePlusSetCharaPalette;
|
||||||
@@ -81,9 +82,10 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
|||||||
_penumbraRemoveTemporaryCollection = Penumbra.Api.Ipc.RemoveTemporaryCollectionByName.Subscriber(pi);
|
_penumbraRemoveTemporaryCollection = Penumbra.Api.Ipc.RemoveTemporaryCollectionByName.Subscriber(pi);
|
||||||
_penumbraRemoveTemporaryMod = Penumbra.Api.Ipc.RemoveTemporaryMod.Subscriber(pi);
|
_penumbraRemoveTemporaryMod = Penumbra.Api.Ipc.RemoveTemporaryMod.Subscriber(pi);
|
||||||
_penumbraAssignTemporaryCollection = Penumbra.Api.Ipc.AssignTemporaryCollection.Subscriber(pi);
|
_penumbraAssignTemporaryCollection = Penumbra.Api.Ipc.AssignTemporaryCollection.Subscriber(pi);
|
||||||
|
_penumbraResolvePaths = Penumbra.Api.Ipc.ResolvePlayerPaths.Subscriber(pi);
|
||||||
|
_penumbraEnabled = Penumbra.Api.Ipc.GetEnabledState.Subscriber(pi);
|
||||||
|
|
||||||
_penumbraGameObjectResourcePathResolved = Penumbra.Api.Ipc.GameObjectResourcePathResolved.Subscriber(pi, (ptr, arg1, arg2) => ResourceLoaded((IntPtr)ptr, arg1, arg2));
|
_penumbraGameObjectResourcePathResolved = Penumbra.Api.Ipc.GameObjectResourcePathResolved.Subscriber(pi, (ptr, arg1, arg2) => ResourceLoaded((IntPtr)ptr, arg1, arg2));
|
||||||
_penumbraModSettingChanged = Penumbra.Api.Ipc.ModSettingChanged.Subscriber(pi, (modsetting, a, b, c) => PenumbraModSettingChangedHandler());
|
|
||||||
|
|
||||||
_glamourerApiVersion = pi.GetIpcSubscriber<int>("Glamourer.ApiVersion");
|
_glamourerApiVersion = pi.GetIpcSubscriber<int>("Glamourer.ApiVersion");
|
||||||
_glamourerGetAllCustomization = pi.GetIpcSubscriber<GameObject?, string>("Glamourer.GetAllCustomizationFromCharacter");
|
_glamourerGetAllCustomization = pi.GetIpcSubscriber<GameObject?, string>("Glamourer.GetAllCustomizationFromCharacter");
|
||||||
@@ -107,7 +109,7 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
|||||||
_customizePlusOnScaleUpdate = pi.GetIpcSubscriber<string?, object>("CustomizePlus.OnScaleUpdate");
|
_customizePlusOnScaleUpdate = pi.GetIpcSubscriber<string?, object>("CustomizePlus.OnScaleUpdate");
|
||||||
|
|
||||||
_customizePlusOnScaleUpdate.Subscribe(OnCustomizePlusScaleChange);
|
_customizePlusOnScaleUpdate.Subscribe(OnCustomizePlusScaleChange);
|
||||||
|
|
||||||
_palettePlusApiVersion = pi.GetIpcSubscriber<string>("PalettePlus.ApiVersion");
|
_palettePlusApiVersion = pi.GetIpcSubscriber<string>("PalettePlus.ApiVersion");
|
||||||
_palettePlusBuildCharaPalette = pi.GetIpcSubscriber<Character, string>("PalettePlus.BuildCharaPalette");
|
_palettePlusBuildCharaPalette = pi.GetIpcSubscriber<Character, string>("PalettePlus.BuildCharaPalette");
|
||||||
_palettePlusSetCharaPalette = pi.GetIpcSubscriber<Character, string, object>("PalettePlus.SetCharaPalette");
|
_palettePlusSetCharaPalette = pi.GetIpcSubscriber<Character, string, object>("PalettePlus.SetCharaPalette");
|
||||||
@@ -125,6 +127,12 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
|||||||
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => HandleActionQueue());
|
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => HandleActionQueue());
|
||||||
Mediator.Subscribe<GposeFrameworkUpdateMessage>(this, (_) => HandleGposeActionQueue());
|
Mediator.Subscribe<GposeFrameworkUpdateMessage>(this, (_) => HandleGposeActionQueue());
|
||||||
Mediator.Subscribe<ZoneSwitchEndMessage>(this, (_) => ClearActionQueue());
|
Mediator.Subscribe<ZoneSwitchEndMessage>(this, (_) => ClearActionQueue());
|
||||||
|
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => CheckPenumbraModPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckPenumbraModPath()
|
||||||
|
{
|
||||||
|
PenumbraModDirectory = GetPenumbraModDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleGposeActionQueue()
|
private void HandleGposeActionQueue()
|
||||||
@@ -142,11 +150,6 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
|||||||
_inGposeQueueMode = on;
|
_inGposeQueueMode = on;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PenumbraModSettingChangedHandler()
|
|
||||||
{
|
|
||||||
Mediator.Publish(new PenumbraModSettingChangedMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearActionQueue()
|
private void ClearActionQueue()
|
||||||
{
|
{
|
||||||
ActionQueue.Clear();
|
ActionQueue.Clear();
|
||||||
@@ -191,7 +194,7 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return _penumbraApiVersion.Invoke() is { Item1: 4, Item2: >= 17 };
|
return _penumbraApiVersion.Invoke() is { Item1: 4, Item2: >= 19 } && _penumbraEnabled.Invoke();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -259,7 +262,6 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
|||||||
_penumbraDispose.Dispose();
|
_penumbraDispose.Dispose();
|
||||||
_penumbraInit.Dispose();
|
_penumbraInit.Dispose();
|
||||||
_penumbraObjectIsRedrawn.Dispose();
|
_penumbraObjectIsRedrawn.Dispose();
|
||||||
_penumbraModSettingChanged.Dispose();
|
|
||||||
_heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange);
|
_heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +413,9 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
|||||||
return _penumbraGetMetaManipulations.Invoke();
|
return _penumbraGetMetaManipulations.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? PenumbraModDirectory()
|
public string? PenumbraModDirectory;
|
||||||
|
|
||||||
|
public string? GetPenumbraModDirectory()
|
||||||
{
|
{
|
||||||
if (!CheckPenumbraApi()) return null;
|
if (!CheckPenumbraApi()) return null;
|
||||||
return _penumbraResolveModDir!.Invoke().ToLowerInvariant();
|
return _penumbraResolveModDir!.Invoke().ToLowerInvariant();
|
||||||
@@ -495,6 +499,11 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public (string[] forward, string[][] reverse) PenumbraResolvePaths(string[] forward, string[] reverse)
|
||||||
|
{
|
||||||
|
return _penumbraResolvePaths.Invoke(forward, reverse);
|
||||||
|
}
|
||||||
|
|
||||||
private void RedrawEvent(IntPtr objectAddress, int objectTableIndex)
|
private void RedrawEvent(IntPtr objectAddress, int objectTableIndex)
|
||||||
{
|
{
|
||||||
Mediator.Publish(new PenumbraRedrawMessage(objectAddress, objectTableIndex));
|
Mediator.Publish(new PenumbraRedrawMessage(objectAddress, objectTableIndex));
|
||||||
@@ -533,7 +542,7 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
|||||||
if (gameObj is Character c)
|
if (gameObj is Character c)
|
||||||
{
|
{
|
||||||
string decodedPalette = Encoding.UTF8.GetString(Convert.FromBase64String(palette));
|
string decodedPalette = Encoding.UTF8.GetString(Convert.FromBase64String(palette));
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(decodedPalette))
|
if (string.IsNullOrEmpty(decodedPalette))
|
||||||
{
|
{
|
||||||
Logger.Verbose("PalettePlus removing for " + c.Address.ToString("X"));
|
Logger.Verbose("PalettePlus removing for " + c.Address.ToString("X"));
|
||||||
@@ -555,7 +564,7 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
|||||||
if (string.IsNullOrEmpty(palette)) return string.Empty;
|
if (string.IsNullOrEmpty(palette)) return string.Empty;
|
||||||
return Convert.ToBase64String(Encoding.UTF8.GetBytes(palette));
|
return Convert.ToBase64String(Encoding.UTF8.GetBytes(palette));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PalettePlusRemovePalette(IntPtr character)
|
public void PalettePlusRemovePalette(IntPtr character)
|
||||||
{
|
{
|
||||||
if (!CheckPalettePlusApi()) return;
|
if (!CheckPalettePlusApi()) return;
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ public class NotificationService : MediatorSubscriberBase
|
|||||||
|
|
||||||
private void PrintErrorChat(string? message)
|
private void PrintErrorChat(string? message)
|
||||||
{
|
{
|
||||||
SeStringBuilder se = new SeStringBuilder().AddText("[Mare Synchronos] ").AddUiForeground("Error: ", 534).AddItalicsOn().AddUiForeground(message ?? string.Empty, 534).AddUiForegroundOff().AddItalicsOff();
|
SeStringBuilder se = new SeStringBuilder().AddText("[Mare Synchronos] Error: " + message);
|
||||||
_chatGui.Print(se.BuiltString);
|
_chatGui.PrintError(se.BuiltString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,18 +11,17 @@ public class OnlinePlayerManager : MediatorSubscriberBase, IDisposable
|
|||||||
{
|
{
|
||||||
private readonly ApiController _apiController;
|
private readonly ApiController _apiController;
|
||||||
private readonly DalamudUtil _dalamudUtil;
|
private readonly DalamudUtil _dalamudUtil;
|
||||||
private readonly PlayerManager _playerManager;
|
|
||||||
private readonly FileCacheManager _fileDbManager;
|
private readonly FileCacheManager _fileDbManager;
|
||||||
private readonly PairManager _pairManager;
|
private readonly PairManager _pairManager;
|
||||||
|
private CharacterData? _lastSentData;
|
||||||
|
|
||||||
public OnlinePlayerManager(ApiController apiController, DalamudUtil dalamudUtil, PlayerManager playerManager,
|
public OnlinePlayerManager(ApiController apiController, DalamudUtil dalamudUtil,
|
||||||
FileCacheManager fileDbManager, PairManager pairManager, MareMediator mediator) : base(mediator)
|
FileCacheManager fileDbManager, PairManager pairManager, MareMediator mediator) : base(mediator)
|
||||||
{
|
{
|
||||||
Logger.Verbose("Creating " + nameof(OnlinePlayerManager));
|
Logger.Verbose("Creating " + nameof(OnlinePlayerManager));
|
||||||
|
|
||||||
_apiController = apiController;
|
_apiController = apiController;
|
||||||
_dalamudUtil = dalamudUtil;
|
_dalamudUtil = dalamudUtil;
|
||||||
_playerManager = playerManager;
|
|
||||||
_fileDbManager = fileDbManager;
|
_fileDbManager = fileDbManager;
|
||||||
_pairManager = pairManager;
|
_pairManager = pairManager;
|
||||||
|
|
||||||
@@ -30,6 +29,20 @@ public class OnlinePlayerManager : MediatorSubscriberBase, IDisposable
|
|||||||
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn());
|
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn());
|
||||||
Mediator.Subscribe<DalamudLogoutMessage>(this, (_) => DalamudUtilOnLogOut());
|
Mediator.Subscribe<DalamudLogoutMessage>(this, (_) => DalamudUtilOnLogOut());
|
||||||
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => FrameworkOnUpdate());
|
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => FrameworkOnUpdate());
|
||||||
|
Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) =>
|
||||||
|
{
|
||||||
|
var newData = ((CharacterDataCreatedMessage)msg).CharacterData.ToAPI();
|
||||||
|
if (_lastSentData == null || _lastSentData != null && !string.Equals(newData.DataHash.Value, _lastSentData.DataHash.Value, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
Logger.Debug("Pushing data for visible players");
|
||||||
|
_lastSentData = newData;
|
||||||
|
PushCharacterData(_pairManager.VisibleUsers);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Debug("Not sending data for " + newData.DataHash.Value);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlayerManagerOnPlayerHasChanged(PlayerChangedMessage msg)
|
private void PlayerManagerOnPlayerHasChanged(PlayerChangedMessage msg)
|
||||||
@@ -78,11 +91,11 @@ public class OnlinePlayerManager : MediatorSubscriberBase, IDisposable
|
|||||||
|
|
||||||
private void PushCharacterData(List<UserData> visiblePlayers)
|
private void PushCharacterData(List<UserData> visiblePlayers)
|
||||||
{
|
{
|
||||||
if (visiblePlayers.Any() && _playerManager.LastCreatedCharacterData != null)
|
if (visiblePlayers.Any() && _lastSentData != null)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await _apiController.PushCharacterData(_playerManager.LastCreatedCharacterData, visiblePlayers).ConfigureAwait(false);
|
await _apiController.PushCharacterData(_lastSentData, visiblePlayers).ConfigureAwait(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using MareSynchronos.API.Data;
|
using MareSynchronos.API.Data;
|
||||||
|
|||||||
@@ -1,279 +0,0 @@
|
|||||||
using MareSynchronos.Factories;
|
|
||||||
using MareSynchronos.Utils;
|
|
||||||
using MareSynchronos.WebAPI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
|
||||||
using MareSynchronos.Models;
|
|
||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
using MareSynchronos.Mediator;
|
|
||||||
#if DEBUG
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace MareSynchronos.Managers;
|
|
||||||
|
|
||||||
public class PlayerManager : MediatorSubscriberBase, IDisposable
|
|
||||||
{
|
|
||||||
private readonly ApiController _apiController;
|
|
||||||
private readonly CharacterDataFactory _characterDataFactory;
|
|
||||||
private readonly DalamudUtil _dalamudUtil;
|
|
||||||
private readonly IpcManager _ipcManager;
|
|
||||||
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 readonly List<PlayerRelatedObject> _playerRelatedObjects = new();
|
|
||||||
|
|
||||||
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
|
|
||||||
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil,
|
|
||||||
MareMediator mediator) : base(mediator)
|
|
||||||
{
|
|
||||||
Logger.Verbose("Creating " + nameof(PlayerManager));
|
|
||||||
|
|
||||||
_apiController = apiController;
|
|
||||||
_ipcManager = ipcManager;
|
|
||||||
_characterDataFactory = characterDataFactory;
|
|
||||||
_dalamudUtil = dalamudUtil;
|
|
||||||
|
|
||||||
Mediator.Subscribe<CustomizePlusMessage>(this, (msg) => CustomizePlusChanged((CustomizePlusMessage)msg));
|
|
||||||
Mediator.Subscribe<HeelsOffsetMessage>(this, (msg) => HeelsOffsetChanged((HeelsOffsetMessage)msg));
|
|
||||||
Mediator.Subscribe<PalettePlusMessage>(this, (msg) => PalettePlusChanged((PalettePlusMessage)msg));
|
|
||||||
Mediator.Subscribe<ConnectedMessage>(this, (_) => ApiControllerOnConnected());
|
|
||||||
Mediator.Subscribe<DisconnectedMessage>(this, (_) => ApiController_Disconnected());
|
|
||||||
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => DalamudUtilOnDelayedFrameworkUpdate());
|
|
||||||
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => DalamudUtilOnFrameworkUpdate());
|
|
||||||
Mediator.Subscribe<TransientResourceChangedMessage>(this, (msg) => HandleTransientResourceLoad((TransientResourceChangedMessage)msg));
|
|
||||||
|
|
||||||
Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected);
|
|
||||||
if (_apiController.IsConnected)
|
|
||||||
{
|
|
||||||
ApiControllerOnConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
_playerRelatedObjects = new List<PlayerRelatedObject>()
|
|
||||||
{
|
|
||||||
new(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.PlayerPointer),
|
|
||||||
new(ObjectKind.MinionOrMount, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((Character*)_dalamudUtil.PlayerPointer)->CompanionObject),
|
|
||||||
new(ObjectKind.Pet, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetPet()),
|
|
||||||
new(ObjectKind.Companion, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetCompanion()),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DalamudUtilOnFrameworkUpdate()
|
|
||||||
{
|
|
||||||
Mediator.Publish(new PlayerRelatedObjectPointerUpdateMessage(_playerRelatedObjects.Select(f => f.CurrentAddress).ToArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleTransientResourceLoad(TransientResourceChangedMessage msg)
|
|
||||||
{
|
|
||||||
foreach (var obj in _playerRelatedObjects)
|
|
||||||
{
|
|
||||||
if (obj.Address == msg.Address && !obj.HasUnprocessedUpdate)
|
|
||||||
{
|
|
||||||
_transientUpdateCts.Cancel();
|
|
||||||
_transientUpdateCts = new CancellationTokenSource();
|
|
||||||
var token = _transientUpdateCts.Token;
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
Logger.Debug("Delaying transient resource load update");
|
|
||||||
await Task.Delay(750, token).ConfigureAwait(false);
|
|
||||||
if (obj.HasUnprocessedUpdate || token.IsCancellationRequested) return;
|
|
||||||
Logger.Debug("Firing transient resource load update");
|
|
||||||
obj.HasTransientsUpdate = true;
|
|
||||||
}, token);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HeelsOffsetChanged(HeelsOffsetMessage change)
|
|
||||||
{
|
|
||||||
var player = _playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
|
|
||||||
if (LastCreatedCharacterData != null && LastCreatedCharacterData.HeelsOffset != change.Offset && !player.IsProcessing)
|
|
||||||
{
|
|
||||||
Logger.Debug("Heels offset changed to " + change.Offset);
|
|
||||||
player.HasTransientsUpdate = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CustomizePlusChanged(CustomizePlusMessage msg)
|
|
||||||
{
|
|
||||||
var change = msg.Data ?? string.Empty;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PalettePlusChanged(PalettePlusMessage msg)
|
|
||||||
{
|
|
||||||
var change = msg.Data ?? string.Empty;
|
|
||||||
var player = _playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
|
|
||||||
if (LastCreatedCharacterData != null && !string.Equals(LastCreatedCharacterData.PalettePlusData, change, StringComparison.Ordinal) && !player.IsProcessing)
|
|
||||||
{
|
|
||||||
Logger.Debug("PalettePlus data changed to " + change);
|
|
||||||
player.HasTransientsUpdate = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Dispose()
|
|
||||||
{
|
|
||||||
base.Dispose();
|
|
||||||
|
|
||||||
_playerChangedCts?.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void DalamudUtilOnDelayedFrameworkUpdate()
|
|
||||||
{
|
|
||||||
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return;
|
|
||||||
|
|
||||||
_playerRelatedObjects.ForEach(k => k.CheckAndUpdateObject());
|
|
||||||
if (_playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && !c.IsProcessing))
|
|
||||||
{
|
|
||||||
OnPlayerOrAttachedObjectsChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApiControllerOnConnected()
|
|
||||||
{
|
|
||||||
Logger.Debug("ApiController Connected");
|
|
||||||
|
|
||||||
Mediator.Subscribe<PenumbraRedrawMessage>(this, (msg) => IpcManager_PenumbraRedrawEvent((PenumbraRedrawMessage)msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApiController_Disconnected()
|
|
||||||
{
|
|
||||||
Logger.Debug(nameof(ApiController_Disconnected));
|
|
||||||
|
|
||||||
Mediator.Unsubscribe<PenumbraRedrawMessage>(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<API.Data.CharacterData?> CreateFullCharacterCacheDto(CancellationToken token)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
if (!token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
unprocessedObject.HasUnprocessedUpdate = false;
|
|
||||||
unprocessedObject.IsProcessing = false;
|
|
||||||
unprocessedObject.HasTransientsUpdate = false;
|
|
||||||
}
|
|
||||||
token.ThrowIfCancellationRequested();
|
|
||||||
}
|
|
||||||
|
|
||||||
int timeOut = 10000;
|
|
||||||
while (!PermanentDataCache.IsReady && !token.IsCancellationRequested && timeOut >= 0)
|
|
||||||
{
|
|
||||||
Logger.Verbose("Waiting until cache is ready (Timeout: " + TimeSpan.FromMilliseconds(timeOut) + ")");
|
|
||||||
await Task.Delay(50, token).ConfigureAwait(false);
|
|
||||||
timeOut -= 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.IsCancellationRequested || timeOut <= 0) return null;
|
|
||||||
|
|
||||||
Logger.Verbose("Cache creation complete");
|
|
||||||
|
|
||||||
var cache = PermanentDataCache.ToAPI();
|
|
||||||
//Logger.Verbose(JsonConvert.SerializeObject(cache, Formatting.Indented));
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void IpcManager_PenumbraRedrawEvent(PenumbraRedrawMessage msg)
|
|
||||||
{
|
|
||||||
Logger.Verbose("RedrawEvent for addr " + msg.Address);
|
|
||||||
|
|
||||||
foreach (var item in _playerRelatedObjects)
|
|
||||||
{
|
|
||||||
if (msg.Address == item.Address)
|
|
||||||
{
|
|
||||||
Logger.Debug("Penumbra redraw Event for " + item.ObjectKind);
|
|
||||||
item.HasUnprocessedUpdate = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && (!c.IsProcessing || (c.IsProcessing && c.DoNotSendUpdate))))
|
|
||||||
{
|
|
||||||
OnPlayerOrAttachedObjectsChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPlayerOrAttachedObjectsChanged()
|
|
||||||
{
|
|
||||||
var unprocessedObjects = _playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList();
|
|
||||||
foreach (var unprocessedObject in unprocessedObjects)
|
|
||||||
{
|
|
||||||
unprocessedObject.IsProcessing = true;
|
|
||||||
}
|
|
||||||
Logger.Debug("Object(s) changed: " + string.Join(", ", unprocessedObjects.Select(c => c.ObjectKind)));
|
|
||||||
bool doNotSendUpdate = unprocessedObjects.All(c => c.DoNotSendUpdate);
|
|
||||||
unprocessedObjects.ForEach(p => p.DoNotSendUpdate = false);
|
|
||||||
_playerChangedCts?.Cancel();
|
|
||||||
_playerChangedCts = new CancellationTokenSource();
|
|
||||||
var token = _playerChangedCts.Token;
|
|
||||||
|
|
||||||
// fix for redraw from anamnesis
|
|
||||||
while ((!_dalamudUtil.IsPlayerPresent || string.Equals(_dalamudUtil.PlayerName, "--", StringComparison.Ordinal)) && !token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Logger.Debug("Waiting Until Player is Present");
|
|
||||||
Thread.Sleep(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Logger.Debug("Cancelled");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_ipcManager.Initialized)
|
|
||||||
{
|
|
||||||
Logger.Warn("Penumbra not active, doing nothing.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
API.Data.CharacterData? cacheData = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Mediator.Publish(new HaltScanMessage("Character creation"));
|
|
||||||
foreach (var item in unprocessedObjects)
|
|
||||||
{
|
|
||||||
_dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, item.ObjectKind == ObjectKind.MinionOrMount ? 1000 : 10000, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheData = (await CreateFullCharacterCacheDto(token).ConfigureAwait(false));
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Mediator.Publish(new ResumeScanMessage("Character creation"));
|
|
||||||
}
|
|
||||||
if (cacheData == null || token.IsCancellationRequested) return;
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
//var json = JsonConvert.SerializeObject(cacheDto, Formatting.Indented);
|
|
||||||
//Logger.Verbose(json);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (string.Equals(LastCreatedCharacterData?.DataHash.Value ?? string.Empty, cacheData.DataHash.Value, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
Logger.Debug("Not sending data, already sent");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LastCreatedCharacterData = cacheData;
|
|
||||||
|
|
||||||
if (_apiController.IsConnected && !token.IsCancellationRequested && !doNotSendUpdate)
|
|
||||||
{
|
|
||||||
Logger.Verbose("Invoking PlayerHasChanged");
|
|
||||||
Mediator.Publish(new PlayerChangedMessage(cacheData));
|
|
||||||
}
|
|
||||||
}, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
using MareSynchronos.API.Data.Enum;
|
using MareSynchronos.API.Data.Enum;
|
||||||
using MareSynchronos.Factories;
|
|
||||||
using MareSynchronos.MareConfiguration;
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.Mediator;
|
using MareSynchronos.Mediator;
|
||||||
using MareSynchronos.Models;
|
|
||||||
using MareSynchronos.Utils;
|
using MareSynchronos.Utils;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
@@ -16,13 +14,11 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
|||||||
|
|
||||||
public IntPtr[] PlayerRelatedPointers = Array.Empty<IntPtr>();
|
public IntPtr[] PlayerRelatedPointers = Array.Empty<IntPtr>();
|
||||||
private readonly string[] _fileTypesToHandle = new[] { "tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk" };
|
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 string PlayerPersistentDataKey => _dalamudUtil.PlayerName + "_" + _dalamudUtil.WorldId;
|
||||||
|
|
||||||
private ConcurrentDictionary<IntPtr, HashSet<string>> TransientResources { get; } = new();
|
private ConcurrentDictionary<IntPtr, HashSet<string>> TransientResources { get; } = new();
|
||||||
private ConcurrentDictionary<ObjectKind, HashSet<FileReplacement>> SemiTransientResources { get; } = new();
|
private ConcurrentDictionary<ObjectKind, HashSet<string>> SemiTransientResources { get; } = new();
|
||||||
public TransientResourceManager(ConfigurationService configurationService, DalamudUtil dalamudUtil, FileReplacementFactory fileReplacementFactory, MareMediator mediator) : base(mediator)
|
public TransientResourceManager(ConfigurationService configurationService, DalamudUtil dalamudUtil, MareMediator mediator) : base(mediator)
|
||||||
{
|
{
|
||||||
_configurationService = configurationService;
|
_configurationService = configurationService;
|
||||||
_dalamudUtil = dalamudUtil;
|
_dalamudUtil = dalamudUtil;
|
||||||
@@ -32,16 +28,8 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
|||||||
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => DalamudUtil_FrameworkUpdate());
|
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => DalamudUtil_FrameworkUpdate());
|
||||||
Mediator.Subscribe<ClassJobChangedMessage>(this, (_) => DalamudUtil_ClassJobChanged());
|
Mediator.Subscribe<ClassJobChangedMessage>(this, (_) => DalamudUtil_ClassJobChanged());
|
||||||
Mediator.Subscribe<PlayerRelatedObjectPointerUpdateMessage>(this, (msg) => PlayerRelatedPointers = ((PlayerRelatedObjectPointerUpdateMessage)msg).RelatedObjects);
|
Mediator.Subscribe<PlayerRelatedObjectPointerUpdateMessage>(this, (msg) => PlayerRelatedPointers = ((PlayerRelatedObjectPointerUpdateMessage)msg).RelatedObjects);
|
||||||
// migrate obsolete data to new format
|
|
||||||
if (File.Exists(PersistentDataCache))
|
|
||||||
{
|
|
||||||
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>());
|
SemiTransientResources.TryAdd(ObjectKind.Player, new HashSet<string>(StringComparer.Ordinal));
|
||||||
if (_configurationService.Current.PlayerPersistentTransientCache.TryGetValue(PlayerPersistentDataKey, out var linesInConfig))
|
if (_configurationService.Current.PlayerPersistentTransientCache.TryGetValue(PlayerPersistentDataKey, out var linesInConfig))
|
||||||
{
|
{
|
||||||
int restored = 0;
|
int restored = 0;
|
||||||
@@ -49,14 +37,9 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var fileReplacement = fileReplacementFactory.Create();
|
Logger.Debug("Loaded persistent transient resource " + file);
|
||||||
fileReplacement.ResolvePath(file);
|
SemiTransientResources[ObjectKind.Player].Add(file);
|
||||||
if (fileReplacement.HasFileReplacement)
|
restored++;
|
||||||
{
|
|
||||||
Logger.Debug("Loaded persistent transient resource " + file);
|
|
||||||
SemiTransientResources[ObjectKind.Player].Add(fileReplacement);
|
|
||||||
restored++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -70,20 +53,12 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
|||||||
|
|
||||||
private void Manager_PenumbraModSettingChanged()
|
private void Manager_PenumbraModSettingChanged()
|
||||||
{
|
{
|
||||||
bool successfulValidation = true;
|
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
Logger.Debug("Penumbra Mod Settings changed, verifying SemiTransientResources");
|
Logger.Debug("Penumbra Mod Settings changed, verifying SemiTransientResources");
|
||||||
foreach (var item in SemiTransientResources)
|
foreach (var item in SemiTransientResources)
|
||||||
{
|
{
|
||||||
item.Value.RemoveWhere(p =>
|
Mediator.Publish(new TransientResourceChangedMessage(_dalamudUtil.PlayerPointer));
|
||||||
{
|
|
||||||
var verified = p.Verify();
|
|
||||||
successfulValidation &= verified;
|
|
||||||
return !verified;
|
|
||||||
});
|
|
||||||
if (!successfulValidation)
|
|
||||||
Mediator.Publish(new TransientResourceChangedMessage(_dalamudUtil.PlayerPointer));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -126,19 +101,19 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
|||||||
return new List<string>();
|
return new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FileReplacement> GetSemiTransientResources(ObjectKind objectKind)
|
public HashSet<string> GetSemiTransientResources(ObjectKind objectKind)
|
||||||
{
|
{
|
||||||
if (SemiTransientResources.TryGetValue(objectKind, out var result))
|
if (SemiTransientResources.TryGetValue(objectKind, out var result))
|
||||||
{
|
{
|
||||||
return result.ToList();
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new List<FileReplacement>();
|
return new HashSet<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Manager_PenumbraResourceLoadEvent(PenumbraResourceLoadMessage msg)
|
private void Manager_PenumbraResourceLoadEvent(PenumbraResourceLoadMessage msg)
|
||||||
{
|
{
|
||||||
var gamePath = msg.GamePath;
|
var gamePath = msg.GamePath.ToLowerInvariant();
|
||||||
var gameObject = msg.GameObject;
|
var gameObject = msg.GameObject;
|
||||||
var filePath = msg.FilePath;
|
var filePath = msg.FilePath;
|
||||||
if (!_fileTypesToHandle.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase)))
|
if (!_fileTypesToHandle.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase)))
|
||||||
@@ -166,14 +141,9 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
|||||||
if (string.Equals(filePath, replacedGamePath, StringComparison.OrdinalIgnoreCase)) return;
|
if (string.Equals(filePath, replacedGamePath, StringComparison.OrdinalIgnoreCase)) return;
|
||||||
|
|
||||||
if (TransientResources[gameObject].Contains(replacedGamePath) ||
|
if (TransientResources[gameObject].Contains(replacedGamePath) ||
|
||||||
SemiTransientResources.Any(r => r.Value.Any(f =>
|
SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f, gamePath, StringComparison.OrdinalIgnoreCase))))
|
||||||
string.Equals(f.GamePaths.First(), replacedGamePath, StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& string.Equals(f.ResolvedPath, filePath, StringComparison.OrdinalIgnoreCase))
|
|
||||||
))
|
|
||||||
{
|
{
|
||||||
Logger.Verbose("Not adding " + replacedGamePath + ":" + filePath);
|
Logger.Verbose("Not adding " + replacedGamePath + ":" + filePath);
|
||||||
Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f.GamePaths.First(), replacedGamePath, StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& string.Equals(f.ResolvedPath, filePath, StringComparison.OrdinalIgnoreCase))).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -183,19 +153,11 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveTransientResource(IntPtr gameObject, FileReplacement fileReplacement)
|
public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind)
|
||||||
{
|
|
||||||
if (TransientResources.ContainsKey(gameObject))
|
|
||||||
{
|
|
||||||
TransientResources[gameObject].RemoveWhere(f => fileReplacement.GamePaths.Any(g => string.Equals(g, f, StringComparison.OrdinalIgnoreCase)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind, Func<string, bool, FileReplacement> createFileReplacement)
|
|
||||||
{
|
{
|
||||||
if (!SemiTransientResources.ContainsKey(objectKind))
|
if (!SemiTransientResources.ContainsKey(objectKind))
|
||||||
{
|
{
|
||||||
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
|
SemiTransientResources[objectKind] = new HashSet<string>(StringComparer.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TransientResources.TryGetValue(gameObject, out var resources))
|
if (!TransientResources.TryGetValue(gameObject, out var resources))
|
||||||
@@ -203,47 +165,16 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SemiTransientResources[objectKind].RemoveWhere(p => !p.Verify());
|
|
||||||
|
|
||||||
var transientResources = resources.ToList();
|
var transientResources = resources.ToList();
|
||||||
Logger.Debug("Persisting " + transientResources.Count + " transient resources");
|
Logger.Debug("Persisting " + transientResources.Count + " transient resources");
|
||||||
foreach (var gamePath in transientResources)
|
foreach (var gamePath in transientResources)
|
||||||
{
|
{
|
||||||
var existingResource = SemiTransientResources[objectKind].Any(f => string.Equals(f.GamePaths.First(), gamePath, StringComparison.OrdinalIgnoreCase));
|
SemiTransientResources[objectKind].Add(gamePath);
|
||||||
if (existingResource)
|
|
||||||
{
|
|
||||||
Logger.Debug("Semi Transient resource replaced: " + gamePath);
|
|
||||||
SemiTransientResources[objectKind].RemoveWhere(f => string.Equals(f.GamePaths.First(), gamePath, StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), arg2: true);
|
|
||||||
if (!fileReplacement.HasFileReplacement)
|
|
||||||
fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), arg2: false);
|
|
||||||
if (fileReplacement.HasFileReplacement)
|
|
||||||
{
|
|
||||||
Logger.Debug("Persisting " + gamePath.ToLowerInvariant());
|
|
||||||
if (SemiTransientResources[objectKind].Add(fileReplacement))
|
|
||||||
{
|
|
||||||
Logger.Debug("Added " + fileReplacement);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Debug("Not added " + fileReplacement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Warn("Issue during transient file persistence", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (objectKind == ObjectKind.Player && SemiTransientResources.TryGetValue(ObjectKind.Player, out var fileReplacements))
|
if (objectKind == ObjectKind.Player && SemiTransientResources.TryGetValue(ObjectKind.Player, out var fileReplacements))
|
||||||
{
|
{
|
||||||
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey]
|
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = fileReplacements;
|
||||||
= fileReplacements.SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
||||||
_configurationService.Save();
|
_configurationService.Save();
|
||||||
}
|
}
|
||||||
TransientResources[gameObject].Clear();
|
TransientResources[gameObject].Clear();
|
||||||
@@ -256,22 +187,26 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
|||||||
SemiTransientResources.Clear();
|
SemiTransientResources.Clear();
|
||||||
if (SemiTransientResources.ContainsKey(ObjectKind.Player))
|
if (SemiTransientResources.ContainsKey(ObjectKind.Player))
|
||||||
{
|
{
|
||||||
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey]
|
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = SemiTransientResources[ObjectKind.Player];
|
||||||
= SemiTransientResources[ObjectKind.Player].SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
||||||
_configurationService.Save();
|
_configurationService.Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AddSemiTransientResource(ObjectKind objectKind, FileReplacement item)
|
internal void AddSemiTransientResource(ObjectKind objectKind, string item)
|
||||||
{
|
{
|
||||||
if (!SemiTransientResources.ContainsKey(objectKind))
|
if (!SemiTransientResources.ContainsKey(objectKind))
|
||||||
{
|
{
|
||||||
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
|
SemiTransientResources[objectKind] = new HashSet<string>(StringComparer.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SemiTransientResources[objectKind].Any(f => string.Equals(f.ResolvedPath, item.ResolvedPath, StringComparison.OrdinalIgnoreCase)))
|
SemiTransientResources[objectKind].Add(item.ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ClearTransientPaths(IntPtr ptr, List<string> list)
|
||||||
|
{
|
||||||
|
if (TransientResources.TryGetValue(ptr, out var set))
|
||||||
{
|
{
|
||||||
SemiTransientResources[objectKind].Add(item);
|
set.RemoveWhere(p => list.Contains(p, StringComparer.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,8 +94,8 @@ public class MarePlugin : MediatorSubscriberBase, IDisposable
|
|||||||
|
|
||||||
_runtimeServiceScope?.Dispose();
|
_runtimeServiceScope?.Dispose();
|
||||||
_runtimeServiceScope = _serviceProvider.CreateScope();
|
_runtimeServiceScope = _serviceProvider.CreateScope();
|
||||||
|
_runtimeServiceScope.ServiceProvider.GetRequiredService<CacheCreationService>();
|
||||||
_runtimeServiceScope.ServiceProvider.GetRequiredService<TransientResourceManager>();
|
_runtimeServiceScope.ServiceProvider.GetRequiredService<TransientResourceManager>();
|
||||||
_runtimeServiceScope.ServiceProvider.GetRequiredService<PlayerManager>();
|
|
||||||
_runtimeServiceScope.ServiceProvider.GetRequiredService<OnlinePlayerManager>();
|
_runtimeServiceScope.ServiceProvider.GetRequiredService<OnlinePlayerManager>();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors></Authors>
|
<Authors></Authors>
|
||||||
<Company></Company>
|
<Company></Company>
|
||||||
<Version>0.7.13</Version>
|
<Version>0.7.14</Version>
|
||||||
<Description></Description>
|
<Description></Description>
|
||||||
<Copyright></Copyright>
|
<Copyright></Copyright>
|
||||||
<PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.2" />
|
||||||
<PackageReference Include="Penumbra.Api" Version="1.0.6" />
|
<PackageReference Include="Penumbra.Api" Version="1.0.7" />
|
||||||
<PackageReference Include="Penumbra.String" Version="1.0.1" />
|
<PackageReference Include="Penumbra.String" Version="1.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
|
using MareSynchronos.Models;
|
||||||
|
|
||||||
namespace MareSynchronos.Mediator;
|
namespace MareSynchronos.Mediator;
|
||||||
|
|
||||||
@@ -35,4 +36,7 @@ public record ResumeScanMessage(string Source) : IMessage;
|
|||||||
|
|
||||||
public record NotificationMessage
|
public record NotificationMessage
|
||||||
(string Title, string Message, NotificationType Type, uint TimeShownOnScreen = 3000) : IMessage;
|
(string Title, string Message, NotificationType Type, uint TimeShownOnScreen = 3000) : IMessage;
|
||||||
|
public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : IMessage;
|
||||||
|
public record CharacterDataCreatedMessage(CharacterData CharacterData) : IMessage;
|
||||||
|
|
||||||
#pragma warning restore MA0048 // File name must match type name
|
#pragma warning restore MA0048 // File name must match type name
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MareSynchronos.Utils;
|
|
||||||
using MareSynchronos.API.Data.Enum;
|
using MareSynchronos.API.Data.Enum;
|
||||||
using MareSynchronos.API.Data;
|
using MareSynchronos.API.Data;
|
||||||
|
|
||||||
@@ -10,7 +9,7 @@ namespace MareSynchronos.Models;
|
|||||||
public class CharacterData
|
public class CharacterData
|
||||||
{
|
{
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public Dictionary<ObjectKind, List<FileReplacement>> FileReplacements { get; set; } = new();
|
public Dictionary<ObjectKind, HashSet<FileReplacement>> FileReplacements { get; set; } = new();
|
||||||
|
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public Dictionary<ObjectKind, string> GlamourerString { get; set; } = new();
|
public Dictionary<ObjectKind, string> GlamourerString { get; set; } = new();
|
||||||
@@ -33,7 +32,7 @@ public class CharacterData
|
|||||||
{
|
{
|
||||||
if (!fileReplacement.HasFileReplacement) return;
|
if (!fileReplacement.HasFileReplacement) return;
|
||||||
|
|
||||||
if (!FileReplacements.ContainsKey(objectKind)) FileReplacements.Add(objectKind, new List<FileReplacement>());
|
if (!FileReplacements.ContainsKey(objectKind)) FileReplacements.Add(objectKind, new HashSet<FileReplacement>());
|
||||||
|
|
||||||
var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => string.Equals(f.ResolvedPath, fileReplacement.ResolvedPath, StringComparison.OrdinalIgnoreCase));
|
var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => string.Equals(f.ResolvedPath, fileReplacement.ResolvedPath, StringComparison.OrdinalIgnoreCase));
|
||||||
if (existingReplacement != null)
|
if (existingReplacement != null)
|
||||||
@@ -57,16 +56,9 @@ public class CharacterData
|
|||||||
};
|
};
|
||||||
}).ToList());
|
}).ToList());
|
||||||
|
|
||||||
Logger.Debug("Adding fileSwaps");
|
|
||||||
foreach (var item in FileReplacements)
|
foreach (var item in FileReplacements)
|
||||||
{
|
{
|
||||||
Logger.Debug("Checking fileSwaps for " + item.Key);
|
|
||||||
var fileSwapsToAdd = item.Value.Where(f => f.IsFileSwap).Select(f => f.ToFileReplacementDto());
|
var fileSwapsToAdd = item.Value.Where(f => f.IsFileSwap).Select(f => f.ToFileReplacementDto());
|
||||||
Logger.Debug("Adding " + fileSwapsToAdd.Count() + " file swaps");
|
|
||||||
foreach (var swap in fileSwapsToAdd)
|
|
||||||
{
|
|
||||||
Logger.Debug("Adding: " + swap.GamePaths.First() + ":" + swap.FileSwapPath);
|
|
||||||
}
|
|
||||||
fileReplacements[item.Key].AddRange(fileSwapsToAdd);
|
fileReplacements[item.Key].AddRange(fileSwapsToAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,79 +1,31 @@
|
|||||||
using System.Text;
|
using System.Text.RegularExpressions;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using MareSynchronos.FileCache;
|
using MareSynchronos.FileCache;
|
||||||
using MareSynchronos.Managers;
|
|
||||||
using MareSynchronos.Utils;
|
|
||||||
using MareSynchronos.API.Data;
|
using MareSynchronos.API.Data;
|
||||||
|
|
||||||
namespace MareSynchronos.Models;
|
namespace MareSynchronos.Models;
|
||||||
|
|
||||||
public class FileReplacement
|
public class FileReplacement
|
||||||
{
|
{
|
||||||
private readonly FileCacheManager _fileDbManager;
|
public FileReplacement(List<string> gamePaths, string filePath, FileCacheManager fileDbManager)
|
||||||
private readonly IpcManager _ipcManager;
|
|
||||||
|
|
||||||
public FileReplacement(FileCacheManager fileDbManager, IpcManager ipcManager)
|
|
||||||
{
|
{
|
||||||
_fileDbManager = fileDbManager;
|
GamePaths = gamePaths.Select(g => g.Replace('\\', '/')).ToList();
|
||||||
_ipcManager = ipcManager;
|
ResolvedPath = filePath.Replace('\\', '/');
|
||||||
|
HashLazy = new(() => !IsFileSwap ? fileDbManager.GetFileCacheByPath(ResolvedPath)?.Hash ?? string.Empty : string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Computed => IsFileSwap || !HasFileReplacement || !string.IsNullOrEmpty(Hash);
|
public bool Computed => IsFileSwap || !HasFileReplacement || !string.IsNullOrEmpty(Hash);
|
||||||
|
|
||||||
public List<string> GamePaths { get; set; } = new();
|
public List<string> GamePaths { get; init; } = new();
|
||||||
|
|
||||||
public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => !string.Equals(p, ResolvedPath, StringComparison.Ordinal));
|
public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => !string.Equals(p, ResolvedPath, StringComparison.Ordinal));
|
||||||
|
|
||||||
public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && !string.Equals(GamePaths[0], ResolvedPath, StringComparison.Ordinal);
|
public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && !string.Equals(GamePaths[0], ResolvedPath, StringComparison.Ordinal);
|
||||||
|
|
||||||
public string Hash { get; private set; } = string.Empty;
|
public string Hash => HashLazy.Value;
|
||||||
|
|
||||||
public string ResolvedPath { get; set; } = string.Empty;
|
private Lazy<string> HashLazy;
|
||||||
|
|
||||||
private void SetResolvedPath(string path)
|
public string ResolvedPath { get; init; } = string.Empty;
|
||||||
{
|
|
||||||
ResolvedPath = path.ToLowerInvariant().Replace('\\', '/');
|
|
||||||
if (!HasFileReplacement || IsFileSwap) return;
|
|
||||||
|
|
||||||
_ = Task.Run(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var cache = _fileDbManager.GetFileCacheByPath(ResolvedPath)!;
|
|
||||||
Hash = cache.Hash;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Warn("Could not set Hash for " + ResolvedPath + ", resetting to original", ex);
|
|
||||||
ResolvedPath = GamePaths[0];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Verify()
|
|
||||||
{
|
|
||||||
if (!IsFileSwap)
|
|
||||||
{
|
|
||||||
var cache = _fileDbManager.GetFileCacheByPath(ResolvedPath);
|
|
||||||
if (cache == null)
|
|
||||||
{
|
|
||||||
Logger.Warn("Replacement Failed verification: " + GamePaths[0]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Hash = cache.Hash;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ResolvePath(GamePaths[0]);
|
|
||||||
|
|
||||||
var success = IsFileSwap;
|
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
Logger.Warn("FileSwap Failed verification: " + GamePaths[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileReplacementData ToFileReplacementDto()
|
public FileReplacementData ToFileReplacementDto()
|
||||||
{
|
{
|
||||||
@@ -87,20 +39,6 @@ public class FileReplacement
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
StringBuilder builder = new();
|
return $"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}";
|
||||||
builder.AppendLine($"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}");
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void ReverseResolvePath(string path)
|
|
||||||
{
|
|
||||||
GamePaths = _ipcManager.PenumbraReverseResolvePlayer(path).ToList();
|
|
||||||
SetResolvedPath(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void ResolvePath(string path)
|
|
||||||
{
|
|
||||||
GamePaths = new List<string> { path };
|
|
||||||
SetResolvedPath(_ipcManager.PenumbraResolvePath(path));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ using System.Runtime.InteropServices;
|
|||||||
using MareSynchronos.Utils;
|
using MareSynchronos.Utils;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
using MareSynchronos.API.Data.Enum;
|
using MareSynchronos.API.Data.Enum;
|
||||||
|
using MareSynchronos.Mediator;
|
||||||
|
|
||||||
namespace MareSynchronos.Models;
|
namespace MareSynchronos.Models;
|
||||||
|
|
||||||
public class PlayerRelatedObject
|
public class GameObjectHandler : MediatorSubscriberBase
|
||||||
{
|
{
|
||||||
|
private readonly MareMediator _mediator;
|
||||||
private readonly Func<IntPtr> getAddress;
|
private readonly Func<IntPtr> getAddress;
|
||||||
|
private readonly bool _sendUpdates;
|
||||||
|
|
||||||
public unsafe Character* Character => (Character*)Address;
|
public unsafe Character* Character => (Character*)Address;
|
||||||
|
|
||||||
@@ -31,26 +34,31 @@ public class PlayerRelatedObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlayerRelatedObject(ObjectKind objectKind, IntPtr address, IntPtr drawObjectAddress, Func<IntPtr> getAddress)
|
public GameObjectHandler(MareMediator mediator, ObjectKind objectKind, Func<IntPtr> getAddress, bool sendUpdates = true) : base(mediator)
|
||||||
{
|
{
|
||||||
|
_mediator = mediator;
|
||||||
ObjectKind = objectKind;
|
ObjectKind = objectKind;
|
||||||
Address = address;
|
|
||||||
DrawObjectAddress = drawObjectAddress;
|
|
||||||
this.getAddress = getAddress;
|
this.getAddress = getAddress;
|
||||||
|
_sendUpdates = sendUpdates;
|
||||||
_name = string.Empty;
|
_name = string.Empty;
|
||||||
|
|
||||||
|
Mediator.Subscribe<TransientResourceChangedMessage>(this, (msg) =>
|
||||||
|
{
|
||||||
|
var actualMsg = (TransientResourceChangedMessage)msg;
|
||||||
|
if (actualMsg.Address != Address || !sendUpdates) return;
|
||||||
|
Mediator.Publish(new CreateCacheForObjectMessage(this));
|
||||||
|
});
|
||||||
|
|
||||||
|
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => CheckAndUpdateObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] EquipSlotData { get; set; } = new byte[40];
|
public byte[] EquipSlotData { get; set; } = new byte[40];
|
||||||
public byte[] CustomizeData { get; set; } = new byte[26];
|
public byte[] CustomizeData { get; set; } = new byte[26];
|
||||||
public byte? HatState { get; set; }
|
public byte? HatState { get; set; }
|
||||||
public byte? VisorWeaponState { get; set; }
|
public byte? VisorWeaponState { get; set; }
|
||||||
|
private bool _doNotSendUpdate;
|
||||||
|
|
||||||
public bool HasTransientsUpdate { get; set; } = false;
|
public unsafe bool CheckAndUpdateObject()
|
||||||
public bool HasUnprocessedUpdate { get; set; } = false;
|
|
||||||
public bool DoNotSendUpdate { get; set; } = false;
|
|
||||||
public bool IsProcessing { get; set; } = false;
|
|
||||||
|
|
||||||
public unsafe void CheckAndUpdateObject()
|
|
||||||
{
|
{
|
||||||
var curPtr = CurrentAddress;
|
var curPtr = CurrentAddress;
|
||||||
if (curPtr != IntPtr.Zero)
|
if (curPtr != IntPtr.Zero)
|
||||||
@@ -68,7 +76,10 @@ public class PlayerRelatedObject
|
|||||||
|
|
||||||
Address = curPtr;
|
Address = curPtr;
|
||||||
DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject;
|
DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject;
|
||||||
HasUnprocessedUpdate = true;
|
if (_sendUpdates && !_doNotSendUpdate)
|
||||||
|
Mediator.Publish(new CreateCacheForObjectMessage(this));
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (Address != IntPtr.Zero || DrawObjectAddress != IntPtr.Zero)
|
else if (Address != IntPtr.Zero || DrawObjectAddress != IntPtr.Zero)
|
||||||
@@ -77,12 +88,14 @@ public class PlayerRelatedObject
|
|||||||
DrawObjectAddress = IntPtr.Zero;
|
DrawObjectAddress = IntPtr.Zero;
|
||||||
Logger.Verbose(ObjectKind + " Changed: " + _name + ", now: " + Address + ", " + DrawObjectAddress);
|
Logger.Verbose(ObjectKind + " Changed: " + _name + ", now: " + Address + ", " + DrawObjectAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe bool CompareAndUpdateByteData(byte* equipSlotData, byte* customizeData)
|
private unsafe bool CompareAndUpdateByteData(byte* equipSlotData, byte* customizeData)
|
||||||
{
|
{
|
||||||
bool hasChanges = false;
|
bool hasChanges = false;
|
||||||
DoNotSendUpdate = false;
|
_doNotSendUpdate = false;
|
||||||
for (int i = 0; i < EquipSlotData.Length; i++)
|
for (int i = 0; i < EquipSlotData.Length; i++)
|
||||||
{
|
{
|
||||||
var data = Marshal.ReadByte((IntPtr)equipSlotData, i);
|
var data = Marshal.ReadByte((IntPtr)equipSlotData, i);
|
||||||
@@ -107,10 +120,10 @@ public class PlayerRelatedObject
|
|||||||
var newWeaponOrVisorState = Marshal.ReadByte((IntPtr)customizeData + 31, 0);
|
var newWeaponOrVisorState = Marshal.ReadByte((IntPtr)customizeData + 31, 0);
|
||||||
if (newHatState != HatState)
|
if (newHatState != HatState)
|
||||||
{
|
{
|
||||||
if (HatState != null && !hasChanges && !HasUnprocessedUpdate)
|
if (HatState != null && !hasChanges)
|
||||||
{
|
{
|
||||||
Logger.Debug("Not Sending Update, only Hat changed");
|
Logger.Debug("Not Sending Update, only Hat changed");
|
||||||
DoNotSendUpdate = true;
|
_doNotSendUpdate = true;
|
||||||
}
|
}
|
||||||
HatState = newHatState;
|
HatState = newHatState;
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
@@ -120,10 +133,10 @@ public class PlayerRelatedObject
|
|||||||
|
|
||||||
if (newWeaponOrVisorState != VisorWeaponState)
|
if (newWeaponOrVisorState != VisorWeaponState)
|
||||||
{
|
{
|
||||||
if (VisorWeaponState != null && !hasChanges && !HasUnprocessedUpdate)
|
if (VisorWeaponState != null && !hasChanges)
|
||||||
{
|
{
|
||||||
Logger.Debug("Not Sending Update, only Visor/Weapon changed");
|
Logger.Debug("Not Sending Update, only Visor/Weapon changed");
|
||||||
DoNotSendUpdate = true;
|
_doNotSendUpdate = true;
|
||||||
}
|
}
|
||||||
VisorWeaponState = newWeaponOrVisorState;
|
VisorWeaponState = newWeaponOrVisorState;
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
@@ -59,7 +59,6 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<PairManager>();
|
collection.AddSingleton<PairManager>();
|
||||||
collection.AddSingleton<ApiController>();
|
collection.AddSingleton<ApiController>();
|
||||||
collection.AddSingleton<PeriodicFileScanner>();
|
collection.AddSingleton<PeriodicFileScanner>();
|
||||||
collection.AddSingleton<FileReplacementFactory>();
|
|
||||||
collection.AddSingleton<MareCharaFileManager>();
|
collection.AddSingleton<MareCharaFileManager>();
|
||||||
collection.AddSingleton<NotificationService>();
|
collection.AddSingleton<NotificationService>();
|
||||||
|
|
||||||
@@ -70,9 +69,9 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<IntroUi>();
|
collection.AddSingleton<IntroUi>();
|
||||||
collection.AddSingleton<DownloadUi>();
|
collection.AddSingleton<DownloadUi>();
|
||||||
|
|
||||||
|
collection.AddScoped<CacheCreationService>();
|
||||||
collection.AddScoped<TransientResourceManager>();
|
collection.AddScoped<TransientResourceManager>();
|
||||||
collection.AddScoped<CharacterDataFactory>();
|
collection.AddScoped<CharacterDataFactory>();
|
||||||
collection.AddScoped<PlayerManager>();
|
|
||||||
collection.AddScoped<OnlinePlayerManager>();
|
collection.AddScoped<OnlinePlayerManager>();
|
||||||
|
|
||||||
var serviceProvider = collection.BuildServiceProvider(new ServiceProviderOptions() { ValidateOnBuild = true, ValidateScopes = true });
|
var serviceProvider = collection.BuildServiceProvider(new ServiceProviderOptions() { ValidateOnBuild = true, ValidateScopes = true });
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public class SettingsUi : WindowMediatorSubscriberBase, IDisposable
|
|||||||
Mediator.Subscribe<SwitchToIntroUiMessage>(this, (_) => IsOpen = false);
|
Mediator.Subscribe<SwitchToIntroUiMessage>(this, (_) => IsOpen = false);
|
||||||
Mediator.Subscribe<GposeStartMessage>(this, (_) => UiShared_GposeStart());
|
Mediator.Subscribe<GposeStartMessage>(this, (_) => UiShared_GposeStart());
|
||||||
Mediator.Subscribe<GposeEndMessage>(this, (_) => UiShared_GposeEnd());
|
Mediator.Subscribe<GposeEndMessage>(this, (_) => UiShared_GposeEnd());
|
||||||
Mediator.Subscribe<PlayerChangedMessage>(this, (msg) => LastCreatedCharacterData = ((PlayerChangedMessage)msg).Data);
|
Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) => LastCreatedCharacterData = ((CharacterDataCreatedMessage)msg).CharacterData.ToAPI());
|
||||||
|
|
||||||
windowSystem.AddWindow(this);
|
windowSystem.AddWindow(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public partial class UiShared : IDisposable
|
|||||||
public string PlayerName => _dalamudUtil.PlayerName;
|
public string PlayerName => _dalamudUtil.PlayerName;
|
||||||
public uint WorldId => _dalamudUtil.WorldId;
|
public uint WorldId => _dalamudUtil.WorldId;
|
||||||
public Dictionary<ushort, string> WorldData => _dalamudUtil.WorldData.Value;
|
public Dictionary<ushort, string> WorldData => _dalamudUtil.WorldData.Value;
|
||||||
public bool HasValidPenumbraModPath => !(_ipcManager.PenumbraModDirectory() ?? string.Empty).IsNullOrEmpty() && Directory.Exists(_ipcManager.PenumbraModDirectory());
|
public bool HasValidPenumbraModPath => !(_ipcManager.PenumbraModDirectory ?? string.Empty).IsNullOrEmpty() && Directory.Exists(_ipcManager.PenumbraModDirectory);
|
||||||
public bool EditTrackerPosition { get; set; }
|
public bool EditTrackerPosition { get; set; }
|
||||||
public ImFontPtr UidFont { get; private set; }
|
public ImFontPtr UidFont { get; private set; }
|
||||||
public bool UidFontBuilt { get; private set; }
|
public bool UidFontBuilt { get; private set; }
|
||||||
@@ -468,7 +468,7 @@ public partial class UiShared : IDisposable
|
|||||||
{
|
{
|
||||||
if (!success) return;
|
if (!success) return;
|
||||||
|
|
||||||
_isPenumbraDirectory = string.Equals(path.ToLowerInvariant(), _ipcManager.PenumbraModDirectory()?.ToLowerInvariant(), StringComparison.Ordinal);
|
_isPenumbraDirectory = string.Equals(path.ToLowerInvariant(), _ipcManager.PenumbraModDirectory?.ToLowerInvariant(), StringComparison.Ordinal);
|
||||||
_isDirectoryWritable = IsDirectoryWritable(path);
|
_isDirectoryWritable = IsDirectoryWritable(path);
|
||||||
_cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => new FileInfo(f).Name.Length != 40);
|
_cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => new FileInfo(f).Name.Length != 40);
|
||||||
_cacheDirectoryIsValidPath = Regex.IsMatch(path, @"^(?:[a-zA-Z]:\\[\w\s\-\\]+?|\/(?:[\w\s\-\/])+?)$", RegexOptions.ECMAScript);
|
_cacheDirectoryIsValidPath = Regex.IsMatch(path, @"^(?:[a-zA-Z]:\\[\w\s\-\\]+?|\/(?:[\w\s\-\/])+?)$", RegexOptions.ECMAScript);
|
||||||
|
|||||||
@@ -226,11 +226,13 @@ public class DalamudUtil : IDisposable
|
|||||||
{
|
{
|
||||||
if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return;
|
if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return;
|
||||||
|
|
||||||
|
Logger.Verbose($"Starting wait for {name} to draw");
|
||||||
|
|
||||||
var obj = (GameObject*)characterAddress;
|
var obj = (GameObject*)characterAddress;
|
||||||
const int tick = 250;
|
const int tick = 250;
|
||||||
int curWaitTime = 0;
|
int curWaitTime = 0;
|
||||||
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
|
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
|
||||||
while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true) && curWaitTime < timeOut) // 0b100000000000 is "still rendering" or something
|
while ((obj->DrawObject == null || (obj->RenderFlags & 0b100000000000) == 0b100000000000) && (!ct?.IsCancellationRequested ?? true) && curWaitTime < timeOut) // 0b100000000000 is "still rendering" or something
|
||||||
{
|
{
|
||||||
Logger.Verbose($"Waiting for {name} to finish drawing");
|
Logger.Verbose($"Waiting for {name} to finish drawing");
|
||||||
curWaitTime += tick;
|
curWaitTime += tick;
|
||||||
|
|||||||
45
MareSynchronos/Utils/FileReplacementComparer.cs
Normal file
45
MareSynchronos/Utils/FileReplacementComparer.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using MareSynchronos.Models;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Utils;
|
||||||
|
|
||||||
|
public class FileReplacementComparer : IEqualityComparer<FileReplacement>
|
||||||
|
{
|
||||||
|
public static FileReplacementComparer Instance => _instance;
|
||||||
|
private static FileReplacementComparer _instance = new();
|
||||||
|
private FileReplacementComparer() { }
|
||||||
|
public bool Equals(FileReplacement? x, FileReplacement? y)
|
||||||
|
{
|
||||||
|
if (x == null || y == null) return false;
|
||||||
|
return x.ResolvedPath.Equals(y.ResolvedPath) && CompareLists(x.GamePaths, y.GamePaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(FileReplacement obj)
|
||||||
|
{
|
||||||
|
return HashCode.Combine(obj.ResolvedPath.GetHashCode(StringComparison.OrdinalIgnoreCase), GetOrderIndependentHashCode(obj.GamePaths));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
|
||||||
|
{
|
||||||
|
int hash = 0;
|
||||||
|
foreach (T element in source)
|
||||||
|
{
|
||||||
|
hash = unchecked(hash +
|
||||||
|
EqualityComparer<T>.Default.GetHashCode(element));
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CompareLists(List<string> list1, List<string> list2)
|
||||||
|
{
|
||||||
|
if (list1.Count != list2.Count)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < list1.Count; i++)
|
||||||
|
{
|
||||||
|
if (!string.Equals(list1[i], list2[i], StringComparison.OrdinalIgnoreCase))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user