Files
LightlessClient/LightlessSync/Utils/VariousExtensions.cs

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;
}
}