mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Add Model Parsing and display them under Changed Items, also display variants there, and rework Data Sharing a bunch.
This commit is contained in:
parent
a64273bd73
commit
eedd3e2dac
21 changed files with 17032 additions and 332 deletions
|
|
@ -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>(),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue