Added seperate collections for other states, moved clean up of penumbra collection out of config. Safe read of ptr on process, fixed notfications on popup and notifications with flags.
This commit is contained in:
41
LightlessSync/Utils/MemoryProcessProbe.cs
Normal file
41
LightlessSync/Utils/MemoryProcessProbe.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LightlessSync.Utils;
|
||||
|
||||
internal static class MemoryProcessProbe
|
||||
{
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern nint GetCurrentProcess();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool ReadProcessMemory(
|
||||
nint hProcess,
|
||||
nint lpBaseAddress,
|
||||
byte[] lpBuffer,
|
||||
int dwSize,
|
||||
out nint lpNumberOfBytesRead);
|
||||
|
||||
private static readonly nint _proc = GetCurrentProcess();
|
||||
|
||||
public static bool TryReadIntPtr(nint address, out nint value)
|
||||
{
|
||||
value = nint.Zero;
|
||||
|
||||
if (address == nint.Zero)
|
||||
return false;
|
||||
|
||||
if ((ulong)address < 0x10000UL)
|
||||
return false;
|
||||
|
||||
var buf = new byte[IntPtr.Size];
|
||||
if (!ReadProcessMemory(_proc, address, buf, buf.Length, out var read) || read != (nint)buf.Length)
|
||||
return false;
|
||||
|
||||
value = IntPtr.Size == 8
|
||||
? (nint)BitConverter.ToInt64(buf, 0)
|
||||
: (nint)BitConverter.ToInt32(buf, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@ using LightlessSync.API.Data;
|
||||
using LightlessSync.API.Data.Enum;
|
||||
using LightlessSync.PlayerData.Pairs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace LightlessSync.Utils;
|
||||
@@ -56,164 +54,168 @@ public static class VariousExtensions
|
||||
return new CancellationTokenSource();
|
||||
}
|
||||
|
||||
public static Dictionary<ObjectKind, HashSet<PlayerChanges>> CheckUpdatedData(this CharacterData newData, Guid applicationBase,
|
||||
CharacterData? oldData, ILogger logger, IPairPerformanceSubject cachedPlayer, bool forceApplyCustomization, bool forceApplyMods,
|
||||
public static Dictionary<ObjectKind, HashSet<PlayerChanges>> CheckUpdatedData(
|
||||
this CharacterData newData,
|
||||
Guid applicationBase,
|
||||
CharacterData? oldData,
|
||||
ILogger logger,
|
||||
IPairPerformanceSubject cachedPlayer,
|
||||
bool forceApplyCustomization,
|
||||
bool forceApplyMods,
|
||||
bool suppressForcedRedrawOnForcedModApply = false)
|
||||
{
|
||||
oldData ??= new();
|
||||
|
||||
static bool HasFiles(List<FileReplacementData>? list) => list is { Count: > 0 };
|
||||
static bool HasText(string? s) => !string.IsNullOrEmpty(s);
|
||||
static string Norm(string? s) => s ?? string.Empty;
|
||||
|
||||
var forceRedrawOnForcedApply = forceApplyMods && !suppressForcedRedrawOnForcedModApply;
|
||||
|
||||
var charaDataToUpdate = new Dictionary<ObjectKind, HashSet<PlayerChanges>>();
|
||||
|
||||
foreach (ObjectKind objectKind in Enum.GetValues<ObjectKind>())
|
||||
{
|
||||
charaDataToUpdate[objectKind] = [];
|
||||
oldData.FileReplacements.TryGetValue(objectKind, out var existingFileReplacements);
|
||||
newData.FileReplacements.TryGetValue(objectKind, out var newFileReplacements);
|
||||
oldData.GlamourerData.TryGetValue(objectKind, out var existingGlamourerData);
|
||||
newData.GlamourerData.TryGetValue(objectKind, out var newGlamourerData);
|
||||
var set = new HashSet<PlayerChanges>();
|
||||
|
||||
bool hasNewButNotOldFileReplacements = newFileReplacements != null && existingFileReplacements == null;
|
||||
bool hasOldButNotNewFileReplacements = existingFileReplacements != null && newFileReplacements == null;
|
||||
oldData.FileReplacements.TryGetValue(objectKind, out var oldFileRepls);
|
||||
newData.FileReplacements.TryGetValue(objectKind, out var newFileRepls);
|
||||
|
||||
bool hasNewButNotOldGlamourerData = newGlamourerData != null && existingGlamourerData == null;
|
||||
bool hasOldButNotNewGlamourerData = existingGlamourerData != null && newGlamourerData == null;
|
||||
oldData.GlamourerData.TryGetValue(objectKind, out var oldGlam);
|
||||
newData.GlamourerData.TryGetValue(objectKind, out var newGlam);
|
||||
|
||||
bool hasNewAndOldFileReplacements = newFileReplacements != null && existingFileReplacements != null;
|
||||
bool hasNewAndOldGlamourerData = newGlamourerData != null && existingGlamourerData != null;
|
||||
var forceRedrawOnForcedApply = forceApplyMods && !suppressForcedRedrawOnForcedModApply;
|
||||
var oldHasFiles = HasFiles(oldFileRepls);
|
||||
var newHasFiles = HasFiles(newFileRepls);
|
||||
|
||||
if (hasNewButNotOldFileReplacements || hasOldButNotNewFileReplacements || hasNewButNotOldGlamourerData || hasOldButNotNewGlamourerData)
|
||||
if (oldHasFiles != newHasFiles)
|
||||
{
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Some new data arrived: NewButNotOldFiles:{hasNewButNotOldFileReplacements}," +
|
||||
" OldButNotNewFiles:{hasOldButNotNewFileReplacements}, NewButNotOldGlam:{hasNewButNotOldGlamourerData}, OldButNotNewGlam:{hasOldButNotNewGlamourerData}) => {change}, {change2}",
|
||||
applicationBase,
|
||||
cachedPlayer, objectKind, hasNewButNotOldFileReplacements, hasOldButNotNewFileReplacements, hasNewButNotOldGlamourerData, hasOldButNotNewGlamourerData, PlayerChanges.ModFiles, PlayerChanges.Glamourer);
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.ModFiles);
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.Glamourer);
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.ForcedRedraw);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hasNewAndOldFileReplacements)
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (File presence changed old={old} new={new}) => {change}",
|
||||
applicationBase, cachedPlayer, objectKind, oldHasFiles, newHasFiles, PlayerChanges.ModFiles);
|
||||
|
||||
set.Add(PlayerChanges.ModFiles);
|
||||
if (objectKind != ObjectKind.Player || forceRedrawOnForcedApply)
|
||||
{
|
||||
var oldList = oldData.FileReplacements[objectKind];
|
||||
var newList = newData.FileReplacements[objectKind];
|
||||
var listsAreEqual = oldList.SequenceEqual(newList, PlayerData.Data.FileReplacementDataComparer.Instance);
|
||||
if (!listsAreEqual || forceApplyMods)
|
||||
{
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (FileReplacements not equal) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.ModFiles);
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.ModFiles);
|
||||
if (objectKind != ObjectKind.Player || forceRedrawOnForcedApply)
|
||||
{
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.ForcedRedraw);
|
||||
}
|
||||
else
|
||||
{
|
||||
var existingFace = existingFileReplacements.Where(g => g.GamePaths.Any(p => p.Contains("/face/", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
var existingHair = existingFileReplacements.Where(g => g.GamePaths.Any(p => p.Contains("/hair/", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
var existingTail = existingFileReplacements.Where(g => g.GamePaths.Any(p => p.Contains("/tail/", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
var newFace = newFileReplacements.Where(g => g.GamePaths.Any(p => p.Contains("/face/", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
var newHair = newFileReplacements.Where(g => g.GamePaths.Any(p => p.Contains("/hair/", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
var newTail = newFileReplacements.Where(g => g.GamePaths.Any(p => p.Contains("/tail/", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
var existingTransients = existingFileReplacements.Where(g => g.GamePaths.Any(g => !g.EndsWith("mdl") && !g.EndsWith("tex") && !g.EndsWith("mtrl")))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
var newTransients = newFileReplacements.Where(g => g.GamePaths.Any(g => !g.EndsWith("mdl") && !g.EndsWith("tex") && !g.EndsWith("mtrl")))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
|
||||
logger.LogTrace("[BASE-{appbase}] ExistingFace: {of}, NewFace: {fc}; ExistingHair: {eh}, NewHair: {nh}; ExistingTail: {et}, NewTail: {nt}; ExistingTransient: {etr}, NewTransient: {ntr}", applicationBase,
|
||||
existingFace.Count, newFace.Count, existingHair.Count, newHair.Count, existingTail.Count, newTail.Count, existingTransients.Count, newTransients.Count);
|
||||
|
||||
var differentFace = !existingFace.SequenceEqual(newFace, PlayerData.Data.FileReplacementDataComparer.Instance);
|
||||
var differentHair = !existingHair.SequenceEqual(newHair, PlayerData.Data.FileReplacementDataComparer.Instance);
|
||||
var differentTail = !existingTail.SequenceEqual(newTail, PlayerData.Data.FileReplacementDataComparer.Instance);
|
||||
var differenTransients = !existingTransients.SequenceEqual(newTransients, PlayerData.Data.FileReplacementDataComparer.Instance);
|
||||
if (differentFace || differentHair || differentTail || differenTransients)
|
||||
{
|
||||
logger.LogDebug("[BASE-{appbase}] Different Subparts: Face: {face}, Hair: {hair}, Tail: {tail}, Transients: {transients} => {change}", applicationBase,
|
||||
differentFace, differentHair, differentTail, differenTransients, PlayerChanges.ForcedRedraw);
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.ForcedRedraw);
|
||||
}
|
||||
}
|
||||
}
|
||||
set.Add(PlayerChanges.ForcedRedraw);
|
||||
}
|
||||
}
|
||||
else if (newHasFiles)
|
||||
{
|
||||
var listsAreEqual = oldFileRepls!.SequenceEqual(newFileRepls!, PlayerData.Data.FileReplacementDataComparer.Instance);
|
||||
|
||||
if (hasNewAndOldGlamourerData)
|
||||
if (!listsAreEqual || forceApplyMods)
|
||||
{
|
||||
bool glamourerDataDifferent = !string.Equals(oldData.GlamourerData[objectKind], newData.GlamourerData[objectKind], StringComparison.Ordinal);
|
||||
if (glamourerDataDifferent || forceApplyCustomization)
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (FileReplacements changed or forceApplyMods) => {change}",
|
||||
applicationBase, cachedPlayer, objectKind, PlayerChanges.ModFiles);
|
||||
|
||||
set.Add(PlayerChanges.ModFiles);
|
||||
|
||||
if (objectKind != ObjectKind.Player || forceRedrawOnForcedApply)
|
||||
{
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Glamourer different) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Glamourer);
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.Glamourer);
|
||||
set.Add(PlayerChanges.ForcedRedraw);
|
||||
}
|
||||
else
|
||||
{
|
||||
var existingFace = oldFileRepls.Where(g => g.GamePaths.Any(p => p.Contains("/face/", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
var existingHair = oldFileRepls.Where(g => g.GamePaths.Any(p => p.Contains("/hair/", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
var existingTail = oldFileRepls.Where(g => g.GamePaths.Any(p => p.Contains("/tail/", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
|
||||
var newFace = newFileRepls!.Where(g => g.GamePaths.Any(p => p.Contains("/face/", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
var newHair = newFileRepls.Where(g => g.GamePaths.Any(p => p.Contains("/hair/", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
var newTail = newFileRepls.Where(g => g.GamePaths.Any(p => p.Contains("/tail/", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
|
||||
var existingTransients = oldFileRepls.Where(g => g.GamePaths.Any(p => !p.EndsWith("mdl", StringComparison.OrdinalIgnoreCase)
|
||||
&& !p.EndsWith("tex", StringComparison.OrdinalIgnoreCase)
|
||||
&& !p.EndsWith("mtrl", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
|
||||
var newTransients = newFileRepls.Where(g => g.GamePaths.Any(p => !p.EndsWith("mdl", StringComparison.OrdinalIgnoreCase)
|
||||
&& !p.EndsWith("tex", StringComparison.OrdinalIgnoreCase)
|
||||
&& !p.EndsWith("mtrl", StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
|
||||
var differentFace = !existingFace.SequenceEqual(newFace, PlayerData.Data.FileReplacementDataComparer.Instance);
|
||||
var differentHair = !existingHair.SequenceEqual(newHair, PlayerData.Data.FileReplacementDataComparer.Instance);
|
||||
var differentTail = !existingTail.SequenceEqual(newTail, PlayerData.Data.FileReplacementDataComparer.Instance);
|
||||
var differentTransients = !existingTransients.SequenceEqual(newTransients, PlayerData.Data.FileReplacementDataComparer.Instance);
|
||||
|
||||
if (differentFace || differentHair || differentTail || differentTransients)
|
||||
set.Add(PlayerChanges.ForcedRedraw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oldData.CustomizePlusData.TryGetValue(objectKind, out var oldCustomizePlusData);
|
||||
newData.CustomizePlusData.TryGetValue(objectKind, out var newCustomizePlusData);
|
||||
var oldGlamNorm = Norm(oldGlam);
|
||||
var newGlamNorm = Norm(newGlam);
|
||||
|
||||
oldCustomizePlusData ??= string.Empty;
|
||||
newCustomizePlusData ??= string.Empty;
|
||||
|
||||
bool customizeDataDifferent = !string.Equals(oldCustomizePlusData, newCustomizePlusData, StringComparison.Ordinal);
|
||||
if (customizeDataDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newCustomizePlusData)))
|
||||
if (!string.Equals(oldGlamNorm, newGlamNorm, StringComparison.Ordinal)
|
||||
|| (forceApplyCustomization && HasText(newGlamNorm)))
|
||||
{
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff customize data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Customize);
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.Customize);
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Glamourer different) => {change}",
|
||||
applicationBase, cachedPlayer, objectKind, PlayerChanges.Glamourer);
|
||||
set.Add(PlayerChanges.Glamourer);
|
||||
}
|
||||
|
||||
if (objectKind != ObjectKind.Player) continue;
|
||||
oldData.CustomizePlusData.TryGetValue(objectKind, out var oldC);
|
||||
newData.CustomizePlusData.TryGetValue(objectKind, out var newC);
|
||||
|
||||
bool manipDataDifferent = !string.Equals(oldData.ManipulationData, newData.ManipulationData, StringComparison.Ordinal);
|
||||
if (manipDataDifferent || forceRedrawOnForcedApply)
|
||||
var oldCNorm = Norm(oldC);
|
||||
var newCNorm = Norm(newC);
|
||||
|
||||
if (!string.Equals(oldCNorm, newCNorm, StringComparison.Ordinal)
|
||||
|| (forceApplyCustomization && HasText(newCNorm)))
|
||||
{
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff manip data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.ModManip);
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.ModManip);
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.ForcedRedraw);
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Customize+ different) => {change}",
|
||||
applicationBase, cachedPlayer, objectKind, PlayerChanges.Customize);
|
||||
set.Add(PlayerChanges.Customize);
|
||||
}
|
||||
|
||||
bool heelsOffsetDifferent = !string.Equals(oldData.HeelsData, newData.HeelsData, StringComparison.Ordinal);
|
||||
if (heelsOffsetDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newData.HeelsData)))
|
||||
if (objectKind == ObjectKind.Player)
|
||||
{
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff heels data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Heels);
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.Heels);
|
||||
var oldManip = Norm(oldData.ManipulationData);
|
||||
var newManip = Norm(newData.ManipulationData);
|
||||
|
||||
if (!string.Equals(oldManip, newManip, StringComparison.Ordinal) || forceRedrawOnForcedApply)
|
||||
{
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Manip different) => {change}",
|
||||
applicationBase, cachedPlayer, objectKind, PlayerChanges.ModManip);
|
||||
set.Add(PlayerChanges.ModManip);
|
||||
set.Add(PlayerChanges.ForcedRedraw);
|
||||
}
|
||||
|
||||
if (!string.Equals(Norm(oldData.HeelsData), Norm(newData.HeelsData), StringComparison.Ordinal)
|
||||
|| (forceApplyCustomization && HasText(newData.HeelsData)))
|
||||
set.Add(PlayerChanges.Heels);
|
||||
|
||||
if (!string.Equals(Norm(oldData.HonorificData), Norm(newData.HonorificData), StringComparison.Ordinal)
|
||||
|| (forceApplyCustomization && HasText(newData.HonorificData)))
|
||||
set.Add(PlayerChanges.Honorific);
|
||||
|
||||
if (!string.Equals(Norm(oldData.MoodlesData), Norm(newData.MoodlesData), StringComparison.Ordinal)
|
||||
|| (forceApplyCustomization && HasText(newData.MoodlesData)))
|
||||
set.Add(PlayerChanges.Moodles);
|
||||
|
||||
if (!string.Equals(Norm(oldData.PetNamesData), Norm(newData.PetNamesData), StringComparison.Ordinal)
|
||||
|| (forceApplyCustomization && HasText(newData.PetNamesData)))
|
||||
set.Add(PlayerChanges.PetNames);
|
||||
}
|
||||
|
||||
bool honorificDataDifferent = !string.Equals(oldData.HonorificData, newData.HonorificData, StringComparison.Ordinal);
|
||||
if (honorificDataDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newData.HonorificData)))
|
||||
{
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff honorific data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Honorific);
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.Honorific);
|
||||
}
|
||||
|
||||
bool moodlesDataDifferent = !string.Equals(oldData.MoodlesData, newData.MoodlesData, StringComparison.Ordinal);
|
||||
if (moodlesDataDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newData.MoodlesData)))
|
||||
{
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff moodles data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Moodles);
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.Moodles);
|
||||
}
|
||||
|
||||
bool petNamesDataDifferent = !string.Equals(oldData.PetNamesData, newData.PetNamesData, StringComparison.Ordinal);
|
||||
if (petNamesDataDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newData.PetNamesData)))
|
||||
{
|
||||
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff petnames data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.PetNames);
|
||||
charaDataToUpdate[objectKind].Add(PlayerChanges.PetNames);
|
||||
}
|
||||
if (set.Count > 0)
|
||||
charaDataToUpdate[objectKind] = set;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<ObjectKind, HashSet<PlayerChanges>> data in charaDataToUpdate.ToList())
|
||||
{
|
||||
if (!data.Value.Any()) charaDataToUpdate.Remove(data.Key);
|
||||
else charaDataToUpdate[data.Key] = [.. data.Value.OrderByDescending(p => (int)p)];
|
||||
}
|
||||
foreach (var k in charaDataToUpdate.Keys.ToList())
|
||||
charaDataToUpdate[k] = [.. charaDataToUpdate[k].OrderBy(p => (int)p)];
|
||||
|
||||
return charaDataToUpdate;
|
||||
}
|
||||
|
||||
|
||||
public static T DeepClone<T>(this T obj)
|
||||
{
|
||||
return JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(obj))!;
|
||||
|
||||
Reference in New Issue
Block a user