233 lines
11 KiB
C#
233 lines
11 KiB
C#
using Dalamud.Game.ClientState.Objects.Types;
|
|
using LightlessSync.API.Data;
|
|
using LightlessSync.API.Data.Enum;
|
|
using LightlessSync.PlayerData.Pairs;
|
|
using Microsoft.Extensions.Logging;
|
|
using System.Text.Json;
|
|
|
|
namespace LightlessSync.Utils;
|
|
|
|
public static class VariousExtensions
|
|
{
|
|
public static string ToByteString(this int bytes, bool addSuffix = true)
|
|
{
|
|
string[] suffix = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
int i;
|
|
double dblSByte = bytes;
|
|
for (i = 0; i < suffix.Length && bytes >= 1024; i++, bytes /= 1024)
|
|
{
|
|
dblSByte = bytes / 1024.0;
|
|
}
|
|
|
|
return addSuffix ? $"{dblSByte:0.00} {suffix[i]}" : $"{dblSByte:0.00}";
|
|
}
|
|
|
|
public static string ToByteString(this long bytes, bool addSuffix = true)
|
|
{
|
|
string[] suffix = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
int i;
|
|
double dblSByte = bytes;
|
|
for (i = 0; i < suffix.Length && bytes >= 1024; i++, bytes /= 1024)
|
|
{
|
|
dblSByte = bytes / 1024.0;
|
|
}
|
|
|
|
return addSuffix ? $"{dblSByte:0.00} {suffix[i]}" : $"{dblSByte:0.00}";
|
|
}
|
|
|
|
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<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>())
|
|
{
|
|
var set = new HashSet<PlayerChanges>();
|
|
|
|
oldData.FileReplacements.TryGetValue(objectKind, out var oldFileRepls);
|
|
newData.FileReplacements.TryGetValue(objectKind, out var newFileRepls);
|
|
|
|
oldData.GlamourerData.TryGetValue(objectKind, out var oldGlam);
|
|
newData.GlamourerData.TryGetValue(objectKind, out var newGlam);
|
|
|
|
var oldHasFiles = HasFiles(oldFileRepls);
|
|
var newHasFiles = HasFiles(newFileRepls);
|
|
|
|
if (oldHasFiles != newHasFiles)
|
|
{
|
|
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)
|
|
{
|
|
set.Add(PlayerChanges.ForcedRedraw);
|
|
}
|
|
}
|
|
else if (newHasFiles)
|
|
{
|
|
var listsAreEqual = oldFileRepls!.SequenceEqual(newFileRepls!, PlayerData.Data.FileReplacementDataComparer.Instance);
|
|
|
|
if (!listsAreEqual || forceApplyMods)
|
|
{
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
var oldGlamNorm = Norm(oldGlam);
|
|
var newGlamNorm = Norm(newGlam);
|
|
|
|
if (!string.Equals(oldGlamNorm, newGlamNorm, StringComparison.Ordinal)
|
|
|| (forceApplyCustomization && HasText(newGlamNorm)))
|
|
{
|
|
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Glamourer different) => {change}",
|
|
applicationBase, cachedPlayer, objectKind, PlayerChanges.Glamourer);
|
|
set.Add(PlayerChanges.Glamourer);
|
|
}
|
|
|
|
oldData.CustomizePlusData.TryGetValue(objectKind, out var oldC);
|
|
newData.CustomizePlusData.TryGetValue(objectKind, out var newC);
|
|
|
|
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} (Customize+ different) => {change}",
|
|
applicationBase, cachedPlayer, objectKind, PlayerChanges.Customize);
|
|
set.Add(PlayerChanges.Customize);
|
|
}
|
|
|
|
if (objectKind == ObjectKind.Player)
|
|
{
|
|
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);
|
|
}
|
|
|
|
if (set.Count > 0)
|
|
charaDataToUpdate[objectKind] = set;
|
|
}
|
|
|
|
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))!;
|
|
}
|
|
|
|
public static unsafe int? ObjectTableIndex(this IGameObject? gameObject)
|
|
{
|
|
if (gameObject == null || gameObject.Address == IntPtr.Zero)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address)->ObjectIndex;
|
|
}
|
|
} |