Add Model Parsing and display them under Changed Items, also display variants there, and rework Data Sharing a bunch.

This commit is contained in:
Ottermandias 2022-11-24 18:25:51 +01:00
parent a64273bd73
commit eedd3e2dac
21 changed files with 17032 additions and 332 deletions

View file

@ -149,11 +149,11 @@ public static class ActorManagerExtensions
var dict = lhs.Kind switch
{
ObjectKind.MountType => manager.Mounts,
ObjectKind.Companion => manager.Companions,
(ObjectKind)15 => manager.Ornaments, // TODO: CS Update
ObjectKind.BattleNpc => manager.BNpcs,
ObjectKind.EventNpc => manager.ENpcs,
ObjectKind.MountType => manager.Data.Mounts,
ObjectKind.Companion => manager.Data.Companions,
(ObjectKind)15 => manager.Data.Ornaments, // TODO: CS Update
ObjectKind.BattleNpc => manager.Data.BNpcs,
ObjectKind.EventNpc => manager.Data.ENpcs,
_ => new Dictionary<uint, string>(),
};

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using Dalamud;
@ -20,25 +21,135 @@ using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
namespace Penumbra.GameData.Actors;
public sealed partial class ActorManager : DataSharer
public sealed partial class ActorManager : IDisposable
{
/// <summary> Worlds available for players. </summary>
public IReadOnlyDictionary<ushort, string> Worlds { get; }
public sealed class ActorManagerData : DataSharer
{
/// <summary> Worlds available for players. </summary>
public IReadOnlyDictionary<ushort, string> Worlds { get; }
/// <summary> Valid Mount names in title case by mount id. </summary>
public IReadOnlyDictionary<uint, string> Mounts { get; }
/// <summary> Valid Mount names in title case by mount id. </summary>
public IReadOnlyDictionary<uint, string> Mounts { get; }
/// <summary> Valid Companion names in title case by companion id. </summary>
public IReadOnlyDictionary<uint, string> Companions { get; }
/// <summary> Valid Companion names in title case by companion id. </summary>
public IReadOnlyDictionary<uint, string> Companions { get; }
/// <summary> Valid ornament names by id. </summary>
public IReadOnlyDictionary<uint, string> Ornaments { get; }
/// <summary> Valid ornament names by id. </summary>
public IReadOnlyDictionary<uint, string> Ornaments { get; }
/// <summary> Valid BNPC names in title case by BNPC Name id. </summary>
public IReadOnlyDictionary<uint, string> BNpcs { get; }
/// <summary> Valid BNPC names in title case by BNPC Name id. </summary>
public IReadOnlyDictionary<uint, string> BNpcs { get; }
/// <summary> Valid ENPC names in title case by ENPC id. </summary>
public IReadOnlyDictionary<uint, string> ENpcs { get; }
/// <summary> Valid ENPC names in title case by ENPC id. </summary>
public IReadOnlyDictionary<uint, string> ENpcs { get; }
public ActorManagerData(DalamudPluginInterface pluginInterface, DataManager gameData, ClientLanguage language)
: base(pluginInterface, language, 1)
{
Worlds = TryCatchData("Worlds", () => CreateWorldData(gameData));
Mounts = TryCatchData("Mounts", () => CreateMountData(gameData));
Companions = TryCatchData("Companions", () => CreateCompanionData(gameData));
Ornaments = TryCatchData("Ornaments", () => CreateOrnamentData(gameData));
BNpcs = TryCatchData("BNpcs", () => CreateBNpcData(gameData));
ENpcs = TryCatchData("ENpcs", () => CreateENpcData(gameData));
}
/// <summary>
/// Return the world name including the Any World option.
/// </summary>
public string ToWorldName(ushort worldId)
=> worldId == ushort.MaxValue ? "Any World" : Worlds.TryGetValue(worldId, out var name) ? name : "Invalid";
/// <summary>
/// Convert a given ID for a certain ObjectKind to a name.
/// </summary>
/// <returns>Invalid or a valid name.</returns>
public string ToName(ObjectKind kind, uint dataId)
=> TryGetName(kind, dataId, out var ret) ? ret : "Invalid";
/// <summary>
/// Convert a given ID for a certain ObjectKind to a name.
/// </summary>
public bool TryGetName(ObjectKind kind, uint dataId, [NotNullWhen(true)] out string? name)
{
name = null;
return kind switch
{
ObjectKind.MountType => Mounts.TryGetValue(dataId, out name),
ObjectKind.Companion => Companions.TryGetValue(dataId, out name),
(ObjectKind)15 => Ornaments.TryGetValue(dataId, out name), // TODO: CS Update
ObjectKind.BattleNpc => BNpcs.TryGetValue(dataId, out name),
ObjectKind.EventNpc => ENpcs.TryGetValue(dataId, out name),
_ => false,
};
}
protected override void DisposeInternal()
{
DisposeTag("Worlds");
DisposeTag("Mounts");
DisposeTag("Companions");
DisposeTag("Ornaments");
DisposeTag("BNpcs");
DisposeTag("ENpcs");
}
private IReadOnlyDictionary<ushort, string> CreateWorldData(DataManager gameData)
=> gameData.GetExcelSheet<World>(Language)!
.Where(w => w.IsPublic && !w.Name.RawData.IsEmpty)
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
private IReadOnlyDictionary<uint, string> CreateMountData(DataManager gameData)
=> gameData.GetExcelSheet<Mount>(Language)!
.Where(m => m.Singular.RawData.Length > 0 && m.Order >= 0)
.ToDictionary(m => m.RowId, m => ToTitleCaseExtended(m.Singular, m.Article));
private IReadOnlyDictionary<uint, string> CreateCompanionData(DataManager gameData)
=> gameData.GetExcelSheet<Companion>(Language)!
.Where(c => c.Singular.RawData.Length > 0 && c.Order < ushort.MaxValue)
.ToDictionary(c => c.RowId, c => ToTitleCaseExtended(c.Singular, c.Article));
private IReadOnlyDictionary<uint, string> CreateOrnamentData(DataManager gameData)
=> gameData.GetExcelSheet<Ornament>(Language)!
.Where(o => o.Singular.RawData.Length > 0)
.ToDictionary(o => o.RowId, o => ToTitleCaseExtended(o.Singular, o.Article));
private IReadOnlyDictionary<uint, string> CreateBNpcData(DataManager gameData)
=> gameData.GetExcelSheet<BNpcName>(Language)!
.Where(n => n.Singular.RawData.Length > 0)
.ToDictionary(n => n.RowId, n => ToTitleCaseExtended(n.Singular, n.Article));
private IReadOnlyDictionary<uint, string> CreateENpcData(DataManager gameData)
=> gameData.GetExcelSheet<ENpcResident>(Language)!
.Where(e => e.Singular.RawData.Length > 0)
.ToDictionary(e => e.RowId, e => ToTitleCaseExtended(e.Singular, e.Article));
private static string ToTitleCaseExtended(SeString s, sbyte article)
{
if (article == 1)
return s.ToDalamudString().ToString();
var sb = new StringBuilder(s.ToDalamudString().ToString());
var lastSpace = true;
for (var i = 0; i < sb.Length; ++i)
{
if (sb[i] == ' ')
{
lastSpace = true;
}
else if (lastSpace)
{
lastSpace = false;
sb[i] = char.ToUpperInvariant(sb[i]);
}
}
return sb.ToString();
}
}
public readonly ActorManagerData Data;
public ActorManager(DalamudPluginInterface pluginInterface, ObjectTable objects, ClientState state, DataManager gameData, GameGui gameGui,
Func<ushort, short> toParentIdx)
@ -47,19 +158,12 @@ public sealed partial class ActorManager : DataSharer
public ActorManager(DalamudPluginInterface pluginInterface, ObjectTable objects, ClientState state, DataManager gameData, GameGui gameGui,
ClientLanguage language, Func<ushort, short> toParentIdx)
: base(pluginInterface, language, 1)
{
_objects = objects;
_gameGui = gameGui;
_clientState = state;
_toParentIdx = toParentIdx;
Worlds = TryCatchData("Worlds", () => CreateWorldData(gameData));
Mounts = TryCatchData("Mounts", () => CreateMountData(gameData));
Companions = TryCatchData("Companions", () => CreateCompanionData(gameData));
Ornaments = TryCatchData("Ornaments", () => CreateOrnamentData(gameData));
BNpcs = TryCatchData("BNpcs", () => CreateBNpcData(gameData));
ENpcs = TryCatchData("ENpcs", () => CreateENpcData(gameData));
Data = new ActorManagerData(pluginInterface, gameData, language);
ActorIdentifier.Manager = this;
@ -100,14 +204,11 @@ public sealed partial class ActorManager : DataSharer
return addon == IntPtr.Zero ? ActorIdentifier.Invalid : GetCurrentPlayer();
}
protected override void DisposeInternal()
public void Dispose()
{
DisposeTag("Worlds");
DisposeTag("Mounts");
DisposeTag("Companions");
DisposeTag("Ornaments");
DisposeTag("BNpcs");
DisposeTag("ENpcs");
Data.Dispose();
if (ActorIdentifier.Manager == this)
ActorIdentifier.Manager = null;
}
~ActorManager()
@ -119,60 +220,6 @@ public sealed partial class ActorManager : DataSharer
private readonly Func<ushort, short> _toParentIdx;
private IReadOnlyDictionary<ushort, string> CreateWorldData(DataManager gameData)
=> gameData.GetExcelSheet<World>(Language)!
.Where(w => w.IsPublic && !w.Name.RawData.IsEmpty)
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
private IReadOnlyDictionary<uint, string> CreateMountData(DataManager gameData)
=> gameData.GetExcelSheet<Mount>(Language)!
.Where(m => m.Singular.RawData.Length > 0 && m.Order >= 0)
.ToDictionary(m => m.RowId, m => ToTitleCaseExtended(m.Singular, m.Article));
private IReadOnlyDictionary<uint, string> CreateCompanionData(DataManager gameData)
=> gameData.GetExcelSheet<Companion>(Language)!
.Where(c => c.Singular.RawData.Length > 0 && c.Order < ushort.MaxValue)
.ToDictionary(c => c.RowId, c => ToTitleCaseExtended(c.Singular, c.Article));
private IReadOnlyDictionary<uint, string> CreateOrnamentData(DataManager gameData)
=> gameData.GetExcelSheet<Ornament>(Language)!
.Where(o => o.Singular.RawData.Length > 0)
.ToDictionary(o => o.RowId, o => ToTitleCaseExtended(o.Singular, o.Article));
private IReadOnlyDictionary<uint, string> CreateBNpcData(DataManager gameData)
=> gameData.GetExcelSheet<BNpcName>(Language)!
.Where(n => n.Singular.RawData.Length > 0)
.ToDictionary(n => n.RowId, n => ToTitleCaseExtended(n.Singular, n.Article));
private IReadOnlyDictionary<uint, string> CreateENpcData(DataManager gameData)
=> gameData.GetExcelSheet<ENpcResident>(Language)!
.Where(e => e.Singular.RawData.Length > 0)
.ToDictionary(e => e.RowId, e => ToTitleCaseExtended(e.Singular, e.Article));
private static string ToTitleCaseExtended(SeString s, sbyte article)
{
if (article == 1)
return s.ToDalamudString().ToString();
var sb = new StringBuilder(s.ToDalamudString().ToString());
var lastSpace = true;
for (var i = 0; i < sb.Length; ++i)
{
if (sb[i] == ' ')
{
lastSpace = true;
}
else if (lastSpace)
{
lastSpace = false;
sb[i] = char.ToUpperInvariant(sb[i]);
}
}
return sb.ToString();
}
[Signature("0F B7 0D ?? ?? ?? ?? C7 85", ScanType = ScanType.StaticAddress)]
private static unsafe ushort* _inspectTitleId = null!;

View file

@ -63,12 +63,6 @@ public partial class ActorManager
}
}
/// <summary>
/// Return the world name including the Any World option.
/// </summary>
public string ToWorldName(ushort worldId)
=> worldId == ushort.MaxValue ? "Any World" : Worlds.TryGetValue(worldId, out var name) ? name : "Invalid";
/// <summary>
/// Use stored data to convert an ActorIdentifier to a string.
/// </summary>
@ -77,17 +71,17 @@ public partial class ActorManager
return id.Type switch
{
IdentifierType.Player => id.HomeWorld != _clientState.LocalPlayer?.HomeWorld.Id
? $"{id.PlayerName} ({ToWorldName(id.HomeWorld)})"
? $"{id.PlayerName} ({Data.ToWorldName(id.HomeWorld)})"
: id.PlayerName.ToString(),
IdentifierType.Retainer => id.PlayerName.ToString(),
IdentifierType.Owned => id.HomeWorld != _clientState.LocalPlayer?.HomeWorld.Id
? $"{id.PlayerName} ({ToWorldName(id.HomeWorld)})'s {ToName(id.Kind, id.DataId)}"
: $"{id.PlayerName}s {ToName(id.Kind, id.DataId)}",
? $"{id.PlayerName} ({Data.ToWorldName(id.HomeWorld)})'s {Data.ToName(id.Kind, id.DataId)}"
: $"{id.PlayerName}s {Data.ToName(id.Kind, id.DataId)}",
IdentifierType.Special => id.Special.ToName(),
IdentifierType.Npc =>
id.Index == ushort.MaxValue
? ToName(id.Kind, id.DataId)
: $"{ToName(id.Kind, id.DataId)} at {id.Index}",
? Data.ToName(id.Kind, id.DataId)
: $"{Data.ToName(id.Kind, id.DataId)} at {id.Index}",
IdentifierType.UnkObject => id.PlayerName.IsEmpty
? $"Unknown Object at {id.Index}"
: $"{id.PlayerName} at {id.Index}",
@ -95,32 +89,6 @@ public partial class ActorManager
};
}
/// <summary>
/// Convert a given ID for a certain ObjectKind to a name.
/// </summary>
/// <returns>Invalid or a valid name.</returns>
public string ToName(ObjectKind kind, uint dataId)
=> TryGetName(kind, dataId, out var ret) ? ret : "Invalid";
/// <summary>
/// Convert a given ID for a certain ObjectKind to a name.
/// </summary>
public bool TryGetName(ObjectKind kind, uint dataId, [NotNullWhen(true)] out string? name)
{
name = null;
return kind switch
{
ObjectKind.MountType => Mounts.TryGetValue(dataId, out name),
ObjectKind.Companion => Companions.TryGetValue(dataId, out name),
(ObjectKind)15 => Ornaments.TryGetValue(dataId, out name), // TODO: CS Update
ObjectKind.BattleNpc => BNpcs.TryGetValue(dataId, out name),
ObjectKind.EventNpc => ENpcs.TryGetValue(dataId, out name),
_ => false,
};
}
/// <summary>
/// Compute an ActorIdentifier from a GameObject. If check is true, the values are checked for validity.
/// </summary>
@ -395,7 +363,7 @@ public partial class ActorManager
/// <summary> Checks if the world is a valid public world or ushort.MaxValue (any world). </summary>
public bool VerifyWorld(ushort worldId)
=> worldId == ushort.MaxValue || Worlds.ContainsKey(worldId);
=> worldId == ushort.MaxValue || Data.Worlds.ContainsKey(worldId);
/// <summary> Verify that the enum value is a specific actor and return the name if it is. </summary>
public static bool VerifySpecial(SpecialActor actor)
@ -418,10 +386,10 @@ public partial class ActorManager
{
return kind switch
{
ObjectKind.MountType => Mounts.ContainsKey(dataId),
ObjectKind.Companion => Companions.ContainsKey(dataId),
(ObjectKind)15 => Ornaments.ContainsKey(dataId), // TODO: CS Update
ObjectKind.BattleNpc => BNpcs.ContainsKey(dataId),
ObjectKind.MountType => Data.Mounts.ContainsKey(dataId),
ObjectKind.Companion => Data.Companions.ContainsKey(dataId),
(ObjectKind)15 => Data.Ornaments.ContainsKey(dataId), // TODO: CS Update
ObjectKind.BattleNpc => Data.BNpcs.ContainsKey(dataId),
_ => false,
};
}
@ -429,11 +397,11 @@ public partial class ActorManager
public bool VerifyNpcData(ObjectKind kind, uint dataId)
=> kind switch
{
ObjectKind.MountType => Mounts.ContainsKey(dataId),
ObjectKind.Companion => Companions.ContainsKey(dataId),
(ObjectKind)15 => Ornaments.ContainsKey(dataId), // TODO: CS Update
ObjectKind.BattleNpc => BNpcs.ContainsKey(dataId),
ObjectKind.EventNpc => ENpcs.ContainsKey(dataId),
ObjectKind.MountType => Data.Mounts.ContainsKey(dataId),
ObjectKind.Companion => Data.Companions.ContainsKey(dataId),
(ObjectKind)15 => Data.Ornaments.ContainsKey(dataId), // TODO: CS Update
ObjectKind.BattleNpc => Data.BNpcs.ContainsKey(dataId),
ObjectKind.EventNpc => Data.ENpcs.ContainsKey(dataId),
_ => false,
};
}