* most of the groups refactoring on client * register OnMethods for group stuff * start implementing client (still pretty broken) * finish implementing new api first iteration * idk rework everything for pair shit (still WIP); goal is to remove PairedClients and GroupPairClients from ApiController * move everything to PairManager, remove dictionaries from APiController * remove admin stuff from client, cleanup * adjust reconnection handling, add new settings, todo still to remove access from old stuff that's marked obsolete from config * add back adding servers, fix intro ui * fix obsolete calls * adjust config namespace * add UI for setting animation/sound permissions to syncshells * add ConfigurationService to hot reload config on change from external * move transient data cache to configuration * add deleting service to ui * fix saving of transient resources * fix group pair user assignments * halt scanner when penumbra inactive, add visible/online/offline split to individual pairs and tags * add presence to syncshell ui * move fullpause from config to server config * fixes in code style * more codestyle * show info icon on player in shells, don't show icon when no changes from default state are made, add online notifs * fixes to intro UI --------- Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
278 lines
11 KiB
C#
278 lines
11 KiB
C#
using MareSynchronos.API.Data.Enum;
|
|
using MareSynchronos.Delegates;
|
|
using MareSynchronos.Factories;
|
|
using MareSynchronos.MareConfiguration;
|
|
using MareSynchronos.Models;
|
|
using MareSynchronos.Utils;
|
|
using System.Collections.Concurrent;
|
|
|
|
namespace MareSynchronos.Managers;
|
|
|
|
|
|
public class TransientResourceManager : IDisposable
|
|
{
|
|
private readonly IpcManager _ipcManager;
|
|
private readonly ConfigurationService _configurationService;
|
|
private readonly DalamudUtil _dalamudUtil;
|
|
public event DrawObjectDelegate? TransientResourceLoaded;
|
|
public IntPtr[] PlayerRelatedPointers = Array.Empty<IntPtr>();
|
|
private readonly string[] _fileTypesToHandle = new[] { "tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk" };
|
|
[Obsolete]
|
|
private string PersistentDataCache => Path.Combine(_configurationService.ConfigurationDirectory, "PersistentTransientData.lst");
|
|
private string PlayerPersistentDataKey => _dalamudUtil.PlayerName + "_" + _dalamudUtil.WorldId;
|
|
|
|
private ConcurrentDictionary<IntPtr, HashSet<string>> TransientResources { get; } = new();
|
|
private ConcurrentDictionary<ObjectKind, HashSet<FileReplacement>> SemiTransientResources { get; } = new();
|
|
public TransientResourceManager(IpcManager manager, ConfigurationService configurationService, DalamudUtil dalamudUtil, FileReplacementFactory fileReplacementFactory)
|
|
{
|
|
manager.PenumbraResourceLoadEvent += Manager_PenumbraResourceLoadEvent;
|
|
manager.PenumbraModSettingChanged += Manager_PenumbraModSettingChanged;
|
|
_ipcManager = manager;
|
|
_configurationService = configurationService;
|
|
_dalamudUtil = dalamudUtil;
|
|
dalamudUtil.FrameworkUpdate += DalamudUtil_FrameworkUpdate;
|
|
dalamudUtil.ClassJobChanged += DalamudUtil_ClassJobChanged;
|
|
// migrate obsolete data to new format
|
|
if (File.Exists(PersistentDataCache))
|
|
{
|
|
var persistentEntities = File.ReadAllLines(PersistentDataCache).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = persistentEntities;
|
|
_configurationService.Save();
|
|
File.Delete(PersistentDataCache);
|
|
}
|
|
|
|
SemiTransientResources.TryAdd(ObjectKind.Player, new HashSet<FileReplacement>());
|
|
if (_configurationService.Current.PlayerPersistentTransientCache.TryGetValue(PlayerPersistentDataKey, out var linesInConfig))
|
|
{
|
|
int restored = 0;
|
|
foreach (var file in linesInConfig)
|
|
{
|
|
try
|
|
{
|
|
var fileReplacement = fileReplacementFactory.Create();
|
|
fileReplacement.ResolvePath(file);
|
|
if (fileReplacement.HasFileReplacement)
|
|
{
|
|
Logger.Debug("Loaded persistent transient resource " + file);
|
|
SemiTransientResources[ObjectKind.Player].Add(fileReplacement);
|
|
restored++;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Warn("Error during loading persistent transient resource " + file, ex);
|
|
}
|
|
|
|
}
|
|
Logger.Debug($"Restored {restored}/{linesInConfig.Count()} semi persistent resources");
|
|
}
|
|
}
|
|
|
|
private void Manager_PenumbraModSettingChanged()
|
|
{
|
|
bool successfulValidation = true;
|
|
Task.Run(() =>
|
|
{
|
|
Logger.Debug("Penumbra Mod Settings changed, verifying SemiTransientResources");
|
|
foreach (var item in SemiTransientResources)
|
|
{
|
|
item.Value.RemoveWhere(p =>
|
|
{
|
|
var verified = p.Verify();
|
|
successfulValidation &= verified;
|
|
return !verified;
|
|
});
|
|
if (!successfulValidation)
|
|
TransientResourceLoaded?.Invoke(_dalamudUtil.PlayerPointer, -1);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void DalamudUtil_ClassJobChanged()
|
|
{
|
|
if (SemiTransientResources.ContainsKey(ObjectKind.Pet))
|
|
{
|
|
SemiTransientResources[ObjectKind.Pet].Clear();
|
|
}
|
|
}
|
|
|
|
private void DalamudUtil_FrameworkUpdate()
|
|
{
|
|
foreach (var item in TransientResources.ToList())
|
|
{
|
|
if (!_dalamudUtil.IsGameObjectPresent(item.Key))
|
|
{
|
|
Logger.Debug("Object not present anymore: " + item.Key.ToString("X"));
|
|
TransientResources.TryRemove(item.Key, out _);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void CleanSemiTransientResources(ObjectKind objectKind)
|
|
{
|
|
if (SemiTransientResources.ContainsKey(objectKind))
|
|
{
|
|
SemiTransientResources[objectKind].Clear();
|
|
}
|
|
}
|
|
|
|
public List<string> GetTransientResources(IntPtr gameObject)
|
|
{
|
|
if (TransientResources.TryGetValue(gameObject, out var result))
|
|
{
|
|
return result.ToList();
|
|
}
|
|
|
|
return new List<string>();
|
|
}
|
|
|
|
public List<FileReplacement> GetSemiTransientResources(ObjectKind objectKind)
|
|
{
|
|
if (SemiTransientResources.TryGetValue(objectKind, out var result))
|
|
{
|
|
return result.ToList();
|
|
}
|
|
|
|
return new List<FileReplacement>();
|
|
}
|
|
|
|
private void Manager_PenumbraResourceLoadEvent(IntPtr gameObject, string gamePath, string filePath)
|
|
{
|
|
if (!_fileTypesToHandle.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
return;
|
|
}
|
|
if (!PlayerRelatedPointers.Contains(gameObject))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!TransientResources.ContainsKey(gameObject))
|
|
{
|
|
TransientResources[gameObject] = new(StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
if (filePath.StartsWith("|", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
filePath = filePath.Split("|")[2];
|
|
}
|
|
|
|
filePath = filePath.ToLowerInvariant().Replace("\\", "/", StringComparison.OrdinalIgnoreCase);
|
|
|
|
var replacedGamePath = gamePath.ToLowerInvariant().Replace("\\", "/", StringComparison.OrdinalIgnoreCase);
|
|
if (string.Equals(filePath, replacedGamePath, StringComparison.OrdinalIgnoreCase)) return;
|
|
|
|
if (TransientResources[gameObject].Contains(replacedGamePath) ||
|
|
SemiTransientResources.Any(r => r.Value.Any(f =>
|
|
string.Equals(f.GamePaths.First(), replacedGamePath, StringComparison.OrdinalIgnoreCase)
|
|
&& string.Equals(f.ResolvedPath, filePath, StringComparison.OrdinalIgnoreCase))
|
|
))
|
|
{
|
|
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
|
|
{
|
|
TransientResources[gameObject].Add(replacedGamePath);
|
|
Logger.Debug($"Adding {replacedGamePath} for {gameObject} ({filePath})");
|
|
TransientResourceLoaded?.Invoke(gameObject, -1);
|
|
}
|
|
}
|
|
|
|
public void RemoveTransientResource(IntPtr gameObject, FileReplacement fileReplacement)
|
|
{
|
|
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))
|
|
{
|
|
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
|
|
}
|
|
|
|
if (!TransientResources.TryGetValue(gameObject, out var resources))
|
|
{
|
|
return;
|
|
}
|
|
|
|
SemiTransientResources[objectKind].RemoveWhere(p => !p.Verify());
|
|
|
|
var transientResources = resources.ToList();
|
|
Logger.Debug("Persisting " + transientResources.Count + " transient resources");
|
|
foreach (var gamePath in transientResources)
|
|
{
|
|
var existingResource = SemiTransientResources[objectKind].Any(f => string.Equals(f.GamePaths.First(), gamePath, StringComparison.OrdinalIgnoreCase));
|
|
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))
|
|
{
|
|
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey]
|
|
= fileReplacements.SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
_configurationService.Save();
|
|
}
|
|
TransientResources[gameObject].Clear();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_dalamudUtil.FrameworkUpdate -= DalamudUtil_FrameworkUpdate;
|
|
_ipcManager.PenumbraResourceLoadEvent -= Manager_PenumbraResourceLoadEvent;
|
|
_dalamudUtil.ClassJobChanged -= DalamudUtil_ClassJobChanged;
|
|
_ipcManager.PenumbraModSettingChanged -= Manager_PenumbraModSettingChanged;
|
|
TransientResources.Clear();
|
|
SemiTransientResources.Clear();
|
|
if (SemiTransientResources.ContainsKey(ObjectKind.Player))
|
|
{
|
|
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey]
|
|
= SemiTransientResources[ObjectKind.Player].SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
_configurationService.Save();
|
|
}
|
|
}
|
|
|
|
internal void AddSemiTransientResource(ObjectKind objectKind, FileReplacement item)
|
|
{
|
|
if (!SemiTransientResources.ContainsKey(objectKind))
|
|
{
|
|
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
|
|
}
|
|
|
|
if (!SemiTransientResources[objectKind].Any(f => string.Equals(f.ResolvedPath, item.ResolvedPath, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
SemiTransientResources[objectKind].Add(item);
|
|
}
|
|
}
|
|
}
|