Reimplemented profile conversion to V5 using some heuristics based on excel sheets

This commit is contained in:
RisaDev
2024-10-18 23:15:46 +03:00
parent 486a5ddbe6
commit 0a8104ccdb
15 changed files with 367 additions and 38 deletions

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>

View File

@@ -0,0 +1,65 @@
using CustomizePlus.GameData.ReverseSearchDictionaries;
using Dalamud.Game.ClientState.Objects.Enums;
using OtterGui.Services;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Structs;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace CustomizePlus.GameData.Data;
/// <summary> A collection service for all the name dictionaries required for reverse name search. </summary>
/// note: this is mvp for profile upgrading purposes, not intended to be used for anything else.
public sealed class ReverseNameDicts(
ReverseSearchDictMount _mounts,
ReverseSearchDictCompanion _companions,
ReverseSearchDictBNpc _bNpcs,
ReverseSearchDictENpc _eNpcs)
: IAsyncService
{
/// <summary> Valid Mount ids by name in title case. </summary>
public readonly ReverseSearchDictMount Mounts = _mounts;
/// <summary> Valid Companion ids by name in title case. </summary>
public readonly ReverseSearchDictCompanion Companions = _companions;
/// <summary> Valid BNPC ids by name in title case. </summary>
public readonly ReverseSearchDictBNpc BNpcs = _bNpcs;
/// <summary> Valid ENPC ids by name in title case. </summary>
public readonly ReverseSearchDictENpc ENpcs = _eNpcs;
/// <summary> Finished when all name dictionaries are finished. </summary>
public Task Awaiter { get; } =
Task.WhenAll(_mounts.Awaiter, _companions.Awaiter, _bNpcs.Awaiter, _eNpcs.Awaiter);
/// <inheritdoc/>
public bool Finished
=> Awaiter.IsCompletedSuccessfully;
/// <summary> Convert a given name for a certain ObjectKind to an ID. </summary>
/// <returns> default or a valid id. </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public uint ToID(ObjectKind kind, string name)
=> TryGetID(kind, name, out var ret) ? ret : default;
/// <summary> Convert a given ID for a certain ObjectKind to a name. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public bool TryGetID(ObjectKind kind, string name, [NotNullWhen(true)] out uint npcId)
{
npcId = default;
return kind switch
{
ObjectKind.MountType => Mounts.TryGetValue(name, out npcId),
ObjectKind.Companion => Companions.TryGetValue(name, out npcId),
ObjectKind.BattleNpc => BNpcs.TryGetValue(name, out npcId),
ObjectKind.EventNpc => ENpcs.TryGetValue(name, out npcId),
_ => false,
};
}
}

View File

@@ -0,0 +1,83 @@
using Dalamud.Plugin.Services;
using Dalamud.Plugin;
using FFXIVClientStructs.FFXIV.Common.Lua;
using OtterGui.Log;
using Penumbra.GameData.Data;
using Penumbra.GameData.DataContainers.Bases;
using Penumbra.GameData.Structs;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CustomizePlus.GameData.ReverseSearchDictionaries.Bases;
/// <summary> A base class for dictionaries from NPC names to their IDs. </summary>
/// <param name="pluginInterface"> The plugin interface. </param>
/// <param name="log"> A logger. </param>
/// <param name="gameData"> The data manger to fetch the data from. </param>
/// <param name="name"> The name of the data share. </param>
/// <param name="version"> The version of the data share. </param>
/// <param name="factory"> The factory function to create the data from. </param>
public abstract class ReverseNameDictionary(
IDalamudPluginInterface pluginInterface,
Logger log,
IDataManager gameData,
string name,
int version,
Func<IReadOnlyDictionary<string, uint>> factory)
: DataSharer<IReadOnlyDictionary<string, uint>>(pluginInterface, log, name, gameData.Language, version, factory),
IReadOnlyDictionary<string, NpcId>
{
/// <inheritdoc/>
public IEnumerator<KeyValuePair<string, NpcId>> GetEnumerator()
=> Value.Select(kvp => new KeyValuePair<string, NpcId>(kvp.Key, new NpcId(kvp.Value))).GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
/// <inheritdoc/>
public int Count
=> Value.Count;
/// <inheritdoc/>
public bool ContainsKey(string key)
=> Value.ContainsKey(key);
/// <inheritdoc/>
public bool TryGetValue(string key, [NotNullWhen(true)] out NpcId value)
{
if (!Value.TryGetValue(key, out var uintVal))
{
value = default;
return false;
}
value = new NpcId(uintVal);
return true;
}
/// <inheritdoc/>
public NpcId this[string key]
=> new NpcId(Value[key]);
/// <inheritdoc/>
public IEnumerable<string> Keys
=> Value.Keys;
/// <inheritdoc/>
public IEnumerable<NpcId> Values
=> Value.Values.Select(k => new NpcId(k));
/// <inheritdoc/>
protected override long ComputeMemory()
=> DataUtility.DictionaryMemory(16, Count) + Keys.Sum(v => v.Length * 2); //this seems to be only used by diagnostics stuff so I don't particularly care for this to be correct.
/// <inheritdoc/>
protected override int ComputeTotalCount()
=> Count;
}

View File

@@ -0,0 +1,33 @@
using Dalamud.Plugin.Services;
using Dalamud.Plugin;
using OtterGui.Log;
using Penumbra.GameData.Data;
using System.Collections.Frozen;
using System.Diagnostics.CodeAnalysis;
using Lumina.Excel.GeneratedSheets;
using CustomizePlus.GameData.ReverseSearchDictionaries.Bases;
namespace CustomizePlus.GameData.ReverseSearchDictionaries;
/// <summary> A dictionary that matches names to battle npc ids. </summary>
public sealed class ReverseSearchDictBNpc(IDalamudPluginInterface pluginInterface, Logger log, IDataManager gameData)
: ReverseNameDictionary(pluginInterface, log, gameData, "ReverseSearchBNpcs", 7, () => CreateBNpcData(gameData))
{
/// <summary> Create the data. </summary>
private static IReadOnlyDictionary<string, uint> CreateBNpcData(IDataManager gameData)
{
var sheet = gameData.GetExcelSheet<BNpcName>(gameData.Language)!;
var dict = new Dictionary<string, uint>((int)sheet.RowCount);
foreach (var n in sheet.Where(n => n.Singular.RawData.Length > 0))
dict.TryAdd(DataUtility.ToTitleCaseExtended(n.Singular, n.Article), n.RowId);
return dict.ToFrozenDictionary();
}
/// <inheritdoc cref="ReverseNameDictionary.TryGetValue"/>
public bool TryGetValue(string key, [NotNullWhen(true)] out uint value)
=> Value.TryGetValue(key, out value);
/// <inheritdoc cref="ReverseNameDictionary.this"/>
public uint this[string key]
=> Value[key];
}

View File

@@ -0,0 +1,33 @@
using Dalamud.Plugin.Services;
using Dalamud.Plugin;
using OtterGui.Log;
using Penumbra.GameData.Data;
using System.Collections.Frozen;
using System.Diagnostics.CodeAnalysis;
using Lumina.Excel.GeneratedSheets;
using CustomizePlus.GameData.ReverseSearchDictionaries.Bases;
namespace CustomizePlus.GameData.ReverseSearchDictionaries;
/// <summary> A dictionary that matches companion names to their ids. </summary>
public sealed class ReverseSearchDictCompanion(IDalamudPluginInterface pluginInterface, Logger log, IDataManager gameData)
: ReverseNameDictionary(pluginInterface, log, gameData, "ReverseSearchCompanions", 7, () => CreateCompanionData(gameData))
{
/// <summary> Create the data. </summary>
private static IReadOnlyDictionary<string, uint> CreateCompanionData(IDataManager gameData)
{
var sheet = gameData.GetExcelSheet<Companion>(gameData.Language)!;
var dict = new Dictionary<string, uint>((int)sheet.RowCount);
foreach (var c in sheet.Where(c => c.Singular.RawData.Length > 0 && c.Order < ushort.MaxValue))
dict.TryAdd(DataUtility.ToTitleCaseExtended(c.Singular, c.Article), c.RowId);
return dict.ToFrozenDictionary();
}
/// <inheritdoc cref="ReverseNameDictionary.TryGetValue"/>
public bool TryGetValue(string key, [NotNullWhen(true)] out uint value)
=> Value.TryGetValue(key, out value);
/// <inheritdoc cref="ReverseNameDictionary.this"/>
public uint this[string key]
=> Value[key];
}

View File

@@ -0,0 +1,33 @@
using Dalamud.Plugin.Services;
using Dalamud.Plugin;
using OtterGui.Log;
using Penumbra.GameData.Data;
using System.Collections.Frozen;
using System.Diagnostics.CodeAnalysis;
using Lumina.Excel.GeneratedSheets;
using CustomizePlus.GameData.ReverseSearchDictionaries.Bases;
namespace CustomizePlus.GameData.ReverseSearchDictionaries;
/// <summary> A dictionary that matches names to event npc ids. </summary>
public sealed class ReverseSearchDictENpc(IDalamudPluginInterface pluginInterface, Logger log, IDataManager gameData)
: ReverseNameDictionary(pluginInterface, log, gameData, "ReverseSearchENpcs", 7, () => CreateENpcData(gameData))
{
/// <summary> Create the data. </summary>
private static IReadOnlyDictionary<string, uint> CreateENpcData(IDataManager gameData)
{
var sheet = gameData.GetExcelSheet<ENpcResident>(gameData.Language)!;
var dict = new Dictionary<string, uint>((int)sheet.RowCount);
foreach (var n in sheet.Where(e => e.Singular.RawData.Length > 0))
dict.TryAdd(DataUtility.ToTitleCaseExtended(n.Singular, n.Article), n.RowId);
return dict.ToFrozenDictionary();
}
/// <inheritdoc cref="ReverseNameDictionary.TryGetValue"/>
public bool TryGetValue(string key, [NotNullWhen(true)] out uint value)
=> Value.TryGetValue(key, out value);
/// <inheritdoc cref="ReverseNameDictionary.this"/>
public uint this[string key]
=> Value[key];
}

View File

@@ -0,0 +1,56 @@
using Dalamud.Plugin.Services;
using Dalamud.Plugin;
using OtterGui.Log;
using Penumbra.GameData.Data;
using System.Collections.Frozen;
using System.Diagnostics.CodeAnalysis;
using Lumina.Excel.GeneratedSheets;
using CustomizePlus.GameData.ReverseSearchDictionaries.Bases;
using Dalamud.Utility;
namespace CustomizePlus.GameData.ReverseSearchDictionaries;
/// <summary> A dictionary that matches names to mount ids. </summary>
public sealed class ReverseSearchDictMount(IDalamudPluginInterface pluginInterface, Logger log, IDataManager gameData)
: ReverseNameDictionary(pluginInterface, log, gameData, "ReverseSearchMounts", 7, () => CreateMountData(gameData))
{
/// <summary> Create the data. </summary>
private static IReadOnlyDictionary<string, uint> CreateMountData(IDataManager gameData)
{
var sheet = gameData.GetExcelSheet<Mount>(gameData.Language)!;
var dict = new Dictionary<string, uint>((int)sheet.RowCount);
// Add some custom data.
dict.TryAdd("Falcon (Porter)", 119);
dict.TryAdd("Hippo Cart (Quest)", 295);
dict.TryAdd("Hippo Cart (Quest)", 296);
dict.TryAdd("Miw Miisv (Quest)", 298);
dict.TryAdd("Moon-hopper (Quest)", 309);
foreach (var m in sheet)
{
if (m.Singular.RawData.Length > 0 && m.Order >= 0)
{
dict.TryAdd(DataUtility.ToTitleCaseExtended(m.Singular, m.Article), m.RowId);
}
else if (m.Unknown18.RawData.Length > 0)
{
// Try to transform some file names into category names.
var whistle = m.Unknown18.ToDalamudString().ToString();
whistle = whistle.Replace("SE_Bt_Etc_", string.Empty)
.Replace("Mount_", string.Empty)
.Replace("_call", string.Empty)
.Replace("Whistle", string.Empty);
dict.TryAdd($"? {whistle} #{m.RowId}", m.RowId);
}
}
return dict.ToFrozenDictionary();
}
/// <inheritdoc cref="ReverseNameDictionary.TryGetValue"/>
public bool TryGetValue(string key, [NotNullWhen(true)] out uint value)
=> Value.TryGetValue(key, out value);
/// <inheritdoc cref="ReverseNameDictionary.this"/>
public uint this[string key]
=> Value[key];
}