using Dalamud.Game.ClientState.Objects.Types; using MareSynchronos.API.Data; using MareSynchronos.API.Data.Enum; using MareSynchronos.PlayerData.Handlers; using MareSynchronos.PlayerData.Pairs; using Microsoft.Extensions.Logging; using System.Text.Json; namespace MareSynchronos.Utils; public static class VariousExtensions { public static void CancelDispose(this CancellationTokenSource? cts) { try { cts?.Cancel(); cts?.Dispose(); } catch (ObjectDisposedException) { // swallow it } } public static CancellationTokenSource CancelRecreate(this CancellationTokenSource? cts) { cts.CancelDispose(); return new CancellationTokenSource(); } public static Dictionary> CheckUpdatedData(this CharacterData newData, Guid applicationBase, CharacterData? oldData, ILogger logger, PairHandler cachedPlayer, bool forceApplyCustomization, bool forceApplyMods) { oldData ??= new(); var charaDataToUpdate = new Dictionary>(); foreach (ObjectKind objectKind in Enum.GetValues()) { 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); bool hasNewButNotOldFileReplacements = newFileReplacements != null && existingFileReplacements == null; bool hasOldButNotNewFileReplacements = existingFileReplacements != null && newFileReplacements == null; bool hasNewButNotOldGlamourerData = newGlamourerData != null && existingGlamourerData == null; bool hasOldButNotNewGlamourerData = existingGlamourerData != null && newGlamourerData == null; bool hasNewAndOldFileReplacements = newFileReplacements != null && existingFileReplacements != null; bool hasNewAndOldGlamourerData = newGlamourerData != null && existingGlamourerData != null; if (hasNewButNotOldFileReplacements || hasOldButNotNewFileReplacements || hasNewButNotOldGlamourerData || hasOldButNotNewGlamourerData) { 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); } else { if (hasNewAndOldFileReplacements) { bool listsAreEqual = oldData.FileReplacements[objectKind].SequenceEqual(newData.FileReplacements[objectKind], 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 (hasNewAndOldGlamourerData) { bool glamourerDataDifferent = !string.Equals(oldData.GlamourerData[objectKind], newData.GlamourerData[objectKind], StringComparison.Ordinal); if (glamourerDataDifferent || forceApplyCustomization) { logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Glamourer different) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Glamourer); charaDataToUpdate[objectKind].Add(PlayerChanges.Glamourer); } } } oldData.CustomizePlusData.TryGetValue(objectKind, out var oldCustomizePlusData); newData.CustomizePlusData.TryGetValue(objectKind, out var newCustomizePlusData); bool customizeDataDifferent = !string.Equals(oldCustomizePlusData, newCustomizePlusData, StringComparison.Ordinal); if (customizeDataDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newCustomizePlusData))) { logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff customize data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Customize); charaDataToUpdate[objectKind].Add(PlayerChanges.Customize); } if (objectKind != ObjectKind.Player) continue; bool manipDataDifferent = !string.Equals(oldData.ManipulationData, newData.ManipulationData, StringComparison.Ordinal); if (manipDataDifferent || forceApplyMods) { logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff manip data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.ModManip); charaDataToUpdate[objectKind].Add(PlayerChanges.ModManip); } bool heelsOffsetDifferent = !string.Equals(oldData.HeelsData, newData.HeelsData, StringComparison.Ordinal); if (heelsOffsetDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newData.HeelsData))) { logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff heels data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Heels); charaDataToUpdate[objectKind].Add(PlayerChanges.Heels); } bool palettePlusDataDifferent = !string.Equals(oldData.PalettePlusData, newData.PalettePlusData, StringComparison.Ordinal); if (palettePlusDataDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newData.PalettePlusData))) { logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff palette data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Palette); charaDataToUpdate[objectKind].Add(PlayerChanges.Palette); } 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); } } foreach (KeyValuePair> data in charaDataToUpdate.ToList()) { if (!data.Value.Any()) charaDataToUpdate.Remove(data.Key); else charaDataToUpdate[data.Key] = [.. data.Value.OrderByDescending(p => (int)p)]; } return charaDataToUpdate; } public static T DeepClone(this T obj) { return JsonSerializer.Deserialize(JsonSerializer.Serialize(obj))!; } public static unsafe int? ObjectTableIndex(this GameObject? gameObject) { if (gameObject == null || gameObject.Address == IntPtr.Zero) { return null; } return ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address)->ObjectIndex; } }