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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
15814
Penumbra.GameData/Data/BNpcNames.cs
Normal file
15814
Penumbra.GameData/Data/BNpcNames.cs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -5,18 +5,22 @@ using Dalamud.Plugin;
|
|||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
/// <summary>
|
||||
/// A container base class that shares data through Dalamud but cares about the used language and version.
|
||||
/// Inheritors should dispose their Dalamud Shares in DisposeInternal via DisposeTag and add them in their constructor via TryCatchData.
|
||||
/// </summary>
|
||||
public abstract class DataSharer : IDisposable
|
||||
{
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly int _version;
|
||||
protected readonly ClientLanguage Language;
|
||||
private bool _disposed;
|
||||
protected readonly DalamudPluginInterface PluginInterface;
|
||||
protected readonly int Version;
|
||||
protected readonly ClientLanguage Language;
|
||||
private bool _disposed;
|
||||
|
||||
protected DataSharer(DalamudPluginInterface pluginInterface, ClientLanguage language, int version)
|
||||
{
|
||||
_pluginInterface = pluginInterface;
|
||||
Language = language;
|
||||
_version = version;
|
||||
PluginInterface = pluginInterface;
|
||||
Language = language;
|
||||
Version = version;
|
||||
}
|
||||
|
||||
protected virtual void DisposeInternal()
|
||||
|
|
@ -36,16 +40,13 @@ public abstract class DataSharer : IDisposable
|
|||
=> Dispose();
|
||||
|
||||
protected void DisposeTag(string tag)
|
||||
=> _pluginInterface.RelinquishData(GetVersionedTag(tag));
|
||||
|
||||
private string GetVersionedTag(string tag)
|
||||
=> $"Penumbra.GameData.{tag}.{Language}.V{_version}";
|
||||
=> PluginInterface.RelinquishData(GetVersionedTag(tag, Language, Version));
|
||||
|
||||
protected T TryCatchData<T>(string tag, Func<T> func) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
return _pluginInterface.GetOrCreateData(GetVersionedTag(tag), func);
|
||||
return PluginInterface.GetOrCreateData(GetVersionedTag(tag, Language, Version), func);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -53,4 +54,24 @@ public abstract class DataSharer : IDisposable
|
|||
return func();
|
||||
}
|
||||
}
|
||||
|
||||
public static void DisposeTag(DalamudPluginInterface pi, string tag, ClientLanguage language, int version)
|
||||
=> pi.RelinquishData(GetVersionedTag(tag, language, version));
|
||||
|
||||
public static T TryCatchData<T>(DalamudPluginInterface pi, string tag, ClientLanguage language, int version, Func<T> func)
|
||||
where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
return pi.GetOrCreateData(GetVersionedTag(tag, language, version), func);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error($"Error creating shared actor data for {tag}:\n{ex}");
|
||||
return func();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetVersionedTag(string tag, ClientLanguage language, int version)
|
||||
=> $"Penumbra.GameData.{tag}.{language}.V{version}";
|
||||
}
|
||||
|
|
|
|||
60
Penumbra.GameData/Data/EquipmentIdentificationList.cs
Normal file
60
Penumbra.GameData/Data/EquipmentIdentificationList.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
internal sealed class EquipmentIdentificationList : KeyList<Item>
|
||||
{
|
||||
private const string Tag = "EquipmentIdentification";
|
||||
|
||||
public EquipmentIdentificationList(DalamudPluginInterface pi, ClientLanguage language, DataManager gameData)
|
||||
: base(pi, Tag, language, ObjectIdentification.IdentificationVersion, CreateEquipmentList(gameData, language))
|
||||
{ }
|
||||
|
||||
public IEnumerable<Item> Between(SetId modelId, EquipSlot slot = EquipSlot.Unknown, byte variant = 0)
|
||||
{
|
||||
if (slot == EquipSlot.Unknown)
|
||||
return Between(ToKey(modelId, 0, 0), ToKey(modelId, (EquipSlot)0xFF, 0xFF));
|
||||
if (variant == 0)
|
||||
return Between(ToKey(modelId, slot, 0), ToKey(modelId, slot, 0xFF));
|
||||
|
||||
return Between(ToKey(modelId, slot, variant), ToKey(modelId, slot, variant));
|
||||
}
|
||||
|
||||
public void Dispose(DalamudPluginInterface pi, ClientLanguage language)
|
||||
=> DataSharer.DisposeTag(pi, Tag, language, ObjectIdentification.IdentificationVersion);
|
||||
|
||||
public static ulong ToKey(SetId modelId, EquipSlot slot, byte variant)
|
||||
=> ((ulong)modelId << 32) | ((ulong)slot << 16) | variant;
|
||||
|
||||
public static ulong ToKey(Item i)
|
||||
{
|
||||
var model = (SetId)((Lumina.Data.Parsing.Quad)i.ModelMain).A;
|
||||
var slot = ((EquipSlot)i.EquipSlotCategory.Row).ToSlot();
|
||||
var variant = (byte)((Lumina.Data.Parsing.Quad)i.ModelMain).B;
|
||||
return ToKey(model, slot, variant);
|
||||
}
|
||||
|
||||
protected override IEnumerable<ulong> ToKeys(Item i)
|
||||
{
|
||||
yield return ToKey(i);
|
||||
}
|
||||
|
||||
protected override bool ValidKey(ulong key)
|
||||
=> key != 0;
|
||||
|
||||
protected override int ValueKeySelector(Item data)
|
||||
=> (int)data.RowId;
|
||||
|
||||
private static IEnumerable<Item> CreateEquipmentList(DataManager gameData, ClientLanguage language)
|
||||
{
|
||||
var items = gameData.GetExcelSheet<Item>(language)!;
|
||||
return items.Where(i => ((EquipSlot)i.EquipSlotCategory.Row).IsEquipmentPiece());
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ using System.Text.RegularExpressions;
|
|||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
|
|
@ -97,7 +98,7 @@ internal class GamePathParser : IGamePathParser
|
|||
[ObjectType.Equipment] = CreateRegexes(@"chara/equipment/e(?'id'\d{4})/texture/v(?'variant'\d{2})_c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})(_[a-z])?_[a-z]\.tex"),
|
||||
[ObjectType.DemiHuman] = CreateRegexes(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/texture/v(?'variant'\d{2})_d\k'id'e\k'equip'_(?'slot'[a-z]{3})(_[a-z])?_[a-z]\.tex"),
|
||||
[ObjectType.Accessory] = CreateRegexes(@"chara/accessory/a(?'id'\d{4})/texture/v(?'variant'\d{2})_c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})_[a-z]\.tex"),
|
||||
[ObjectType.Character] = CreateRegexes(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/texture/(?'minus'(--)?)(v(?'variant'\d{2})_)?c\k'race'\k'typeabr'\k'id'(_(?'slot'[a-z]{3}))?(_[a-z])?_[a-z]\.tex"
|
||||
[ObjectType.Character] = CreateRegexes( @"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/texture/(?'minus'(--)?)(v(?'variant'\d{2})_)?c\k'race'\k'typeabr'\k'id'(_(?'slot'[a-z]{3}))?(_[a-z])?_[a-z]\.tex"
|
||||
, @"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/texture"
|
||||
, @"chara/common/texture/skin(?'skin'.*)\.tex"
|
||||
, @"chara/common/texture/(?'catchlight'catchlight)(.*)\.tex"
|
||||
|
|
@ -114,12 +115,12 @@ internal class GamePathParser : IGamePathParser
|
|||
},
|
||||
[FileType.Material] = new Dictionary<ObjectType, IReadOnlyList<Regex>>
|
||||
{
|
||||
[ObjectType.Weapon] = CreateRegexes(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/material/v(?'variant'\d{4})/mt_w\k'id'b\k'weapon'_[a-z]\.mtrl"),
|
||||
[ObjectType.Monster] = CreateRegexes(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/material/v(?'variant'\d{4})/mt_m\k'monster'b\k'id'_[a-z]\.mtrl"),
|
||||
[ObjectType.Equipment] = CreateRegexes(@"chara/equipment/e(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})_[a-z]\.mtrl"),
|
||||
[ObjectType.DemiHuman] = CreateRegexes(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/material/v(?'variant'\d{4})/mt_d\k'id'e\k'equip'_(?'slot'[a-z]{3})_[a-z]\.mtrl"),
|
||||
[ObjectType.Accessory] = CreateRegexes(@"chara/accessory/a(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})_[a-z]\.mtrl"),
|
||||
[ObjectType.Character] = CreateRegexes(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/material(/v(?'variant'\d{4}))?/mt_c\k'race'\k'typeabr'\k'id'(_(?'slot'[a-z]{3}))?_[a-z]\.mtrl"),
|
||||
[ObjectType.Weapon] = CreateRegexes(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/material/v(?'variant'\d{4})/mt_w\k'id'b\k'weapon'_[a-z]+\.mtrl"),
|
||||
[ObjectType.Monster] = CreateRegexes(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/material/v(?'variant'\d{4})/mt_m\k'monster'b\k'id'_[a-z]+\.mtrl"),
|
||||
[ObjectType.Equipment] = CreateRegexes(@"chara/equipment/e(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})_[a-z]+\.mtrl"),
|
||||
[ObjectType.DemiHuman] = CreateRegexes(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/material/v(?'variant'\d{4})/mt_d\k'id'e\k'equip'_(?'slot'[a-z]{3})_[a-z]+\.mtrl"),
|
||||
[ObjectType.Accessory] = CreateRegexes(@"chara/accessory/a(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})_[a-z]+\.mtrl"),
|
||||
[ObjectType.Character] = CreateRegexes(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/material(/v(?'variant'\d{4}))?/mt_c\k'race'\k'typeabr'\k'id'(_(?'slot'[a-z]{3}))?_[a-z]+\.mtrl"),
|
||||
},
|
||||
[FileType.Imc] = new Dictionary<ObjectType, IReadOnlyList<Regex>>
|
||||
{
|
||||
|
|
@ -129,12 +130,12 @@ internal class GamePathParser : IGamePathParser
|
|||
[ObjectType.DemiHuman] = CreateRegexes(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/e\k'equip'\.imc"),
|
||||
[ObjectType.Accessory] = CreateRegexes(@"chara/accessory/a(?'id'\d{4})/a\k'id'\.imc"),
|
||||
},
|
||||
};
|
||||
// @formatter:on
|
||||
|
||||
};
|
||||
|
||||
private static IReadOnlyList<Regex> CreateRegexes(params string[] regexes)
|
||||
=> regexes.Select(s => new Regex(s, RegexOptions.Compiled)).ToArray();
|
||||
=> regexes.Select(s => new Regex(s, RegexOptions.Compiled)).ToArray();
|
||||
// @formatter:on
|
||||
|
||||
|
||||
public ObjectType PathToObjectType(string path)
|
||||
{
|
||||
|
|
|
|||
101
Penumbra.GameData/Data/KeyList.cs
Normal file
101
Penumbra.GameData/Data/KeyList.cs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Plugin;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
/// <summary>
|
||||
/// A list sorting objects based on a key which then allows efficiently finding all objects between a pair of keys via binary search.
|
||||
/// </summary>
|
||||
public abstract class KeyList<T>
|
||||
{
|
||||
private readonly List<(ulong Key, T Data)> _list;
|
||||
|
||||
public IReadOnlyList<(ulong Key, T Data)> List
|
||||
=> _list;
|
||||
|
||||
/// <summary>
|
||||
/// Iterate over all objects between the given minimal and maximal keys (inclusive).
|
||||
/// </summary>
|
||||
protected IEnumerable<T> Between(ulong minKey, ulong maxKey)
|
||||
{
|
||||
var (minIdx, maxIdx) = GetMinMax(minKey, maxKey);
|
||||
if (minIdx < 0)
|
||||
yield break;
|
||||
|
||||
for (var i = minIdx; i <= maxIdx; ++i)
|
||||
yield return _list[i].Data;
|
||||
}
|
||||
|
||||
private (int MinIdx, int MaxIdx) GetMinMax(ulong minKey, ulong maxKey)
|
||||
{
|
||||
var idx = _list.BinarySearch((minKey, default!), ListComparer);
|
||||
var minIdx = idx;
|
||||
if (minIdx < 0)
|
||||
{
|
||||
minIdx = ~minIdx;
|
||||
if (minIdx == _list.Count || _list[minIdx].Key > maxKey)
|
||||
return (-1, -1);
|
||||
|
||||
idx = minIdx;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (minIdx > 0 && _list[minIdx - 1].Key >= minKey)
|
||||
--minIdx;
|
||||
}
|
||||
|
||||
if (_list[minIdx].Key < minKey || _list[minIdx].Key > maxKey)
|
||||
return (-1, -1);
|
||||
|
||||
|
||||
var maxIdx = _list.BinarySearch(idx, _list.Count - idx, (maxKey, default!), ListComparer);
|
||||
if (maxIdx < 0)
|
||||
{
|
||||
maxIdx = ~maxIdx;
|
||||
return maxIdx > minIdx ? (minIdx, maxIdx - 1) : (-1, -1);
|
||||
}
|
||||
|
||||
while (maxIdx < _list.Count - 1 && _list[maxIdx + 1].Key <= maxKey)
|
||||
++maxIdx;
|
||||
|
||||
if (_list[maxIdx].Key < minKey || _list[maxIdx].Key > maxKey)
|
||||
return (-1, -1);
|
||||
|
||||
return (minIdx, maxIdx);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The function turning an object to (potentially multiple) keys. Only used during construction.
|
||||
/// </summary>
|
||||
protected abstract IEnumerable<ulong> ToKeys(T data);
|
||||
|
||||
/// <summary>
|
||||
/// Whether a returned key is valid. Only used during construction.
|
||||
/// </summary>
|
||||
protected abstract bool ValidKey(ulong key);
|
||||
|
||||
/// <summary>
|
||||
/// How multiple items with the same key should be sorted.
|
||||
/// </summary>
|
||||
protected abstract int ValueKeySelector(T data);
|
||||
|
||||
protected KeyList(DalamudPluginInterface pi, string tag, ClientLanguage language, int version, IEnumerable<T> data)
|
||||
{
|
||||
_list = DataSharer.TryCatchData(pi, tag, language, version,
|
||||
() => data.SelectMany(d => ToKeys(d).Select(k => (k, d)))
|
||||
.Where(p => ValidKey(p.k))
|
||||
.OrderBy(p => p.k)
|
||||
.ThenBy(p => ValueKeySelector(p.d))
|
||||
.ToList());
|
||||
}
|
||||
|
||||
private class Comparer : IComparer<(ulong, T)>
|
||||
{
|
||||
public int Compare((ulong, T) x, (ulong, T) y)
|
||||
=> x.Item1.CompareTo(y.Item1);
|
||||
}
|
||||
|
||||
private static readonly Comparer ListComparer = new();
|
||||
}
|
||||
52
Penumbra.GameData/Data/ModelIdentificationList.cs
Normal file
52
Penumbra.GameData/Data/ModelIdentificationList.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using System.Collections.Generic;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
internal sealed class ModelIdentificationList : KeyList<ModelChara>
|
||||
{
|
||||
private const string Tag = "ModelIdentification";
|
||||
|
||||
public ModelIdentificationList(DalamudPluginInterface pi, ClientLanguage language, DataManager gameData)
|
||||
: base(pi, Tag, language, ObjectIdentification.IdentificationVersion, CreateModelList(gameData, language))
|
||||
{ }
|
||||
|
||||
public IEnumerable<ModelChara> Between(CharacterBase.ModelType type, SetId modelId, byte modelBase = 0, byte variant = 0)
|
||||
{
|
||||
if (modelBase == 0)
|
||||
return Between(ToKey(type, modelId, 0, 0), ToKey(type, modelId, 0xFF, 0xFF));
|
||||
if (variant == 0)
|
||||
return Between(ToKey(type, modelId, modelBase, 0), ToKey(type, modelId, modelBase, 0xFF));
|
||||
|
||||
return Between(ToKey(type, modelId, modelBase, variant), ToKey(type, modelId, modelBase, variant));
|
||||
}
|
||||
|
||||
public void Dispose(DalamudPluginInterface pi, ClientLanguage language)
|
||||
=> DataSharer.DisposeTag(pi, Tag, language, ObjectIdentification.IdentificationVersion);
|
||||
|
||||
|
||||
public static ulong ToKey(CharacterBase.ModelType type, SetId model, byte modelBase, byte variant)
|
||||
=> ((ulong)type << 32) | ((ulong)model << 16) | ((ulong)modelBase << 8) | variant;
|
||||
|
||||
private static ulong ToKey(ModelChara row)
|
||||
=> ToKey((CharacterBase.ModelType)row.Type, row.Model, row.Base, row.Variant);
|
||||
|
||||
protected override IEnumerable<ulong> ToKeys(ModelChara row)
|
||||
{
|
||||
yield return ToKey(row);
|
||||
}
|
||||
|
||||
protected override bool ValidKey(ulong key)
|
||||
=> key != 0;
|
||||
|
||||
protected override int ValueKeySelector(ModelChara data)
|
||||
=> (int)data.RowId;
|
||||
|
||||
private static IEnumerable<ModelChara> CreateModelList(DataManager gameData, ClientLanguage language)
|
||||
=> gameData.GetExcelSheet<ModelChara>(language)!;
|
||||
}
|
||||
|
|
@ -10,13 +10,41 @@ using System.Linq;
|
|||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Action = Lumina.Excel.GeneratedSheets.Action;
|
||||
|
||||
using ObjectType = Penumbra.GameData.Enums.ObjectType;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
||||
{
|
||||
public IGamePathParser GamePathParser { get; } = new GamePathParser();
|
||||
public const int IdentificationVersion = 1;
|
||||
|
||||
public IGamePathParser GamePathParser { get; } = new GamePathParser();
|
||||
public readonly IReadOnlyList<IReadOnlyList<uint>> BnpcNames;
|
||||
public readonly IReadOnlyList<IReadOnlyList<(string Name, ObjectKind Kind)>> ModelCharaToObjects;
|
||||
public readonly IReadOnlyDictionary<string, IReadOnlyList<Action>> Actions;
|
||||
private readonly ActorManager.ActorManagerData _actorData;
|
||||
|
||||
|
||||
private readonly EquipmentIdentificationList _equipment;
|
||||
private readonly WeaponIdentificationList _weapons;
|
||||
private readonly ModelIdentificationList _modelIdentifierToModelChara;
|
||||
|
||||
public ObjectIdentification(DalamudPluginInterface pluginInterface, DataManager dataManager, ClientLanguage language)
|
||||
: base(pluginInterface, language, IdentificationVersion)
|
||||
{
|
||||
_actorData = new ActorManager.ActorManagerData(pluginInterface, dataManager, language);
|
||||
_equipment = new EquipmentIdentificationList(pluginInterface, language, dataManager);
|
||||
_weapons = new WeaponIdentificationList(pluginInterface, language, dataManager);
|
||||
Actions = TryCatchData("Actions", () => CreateActionList(dataManager));
|
||||
_equipment = new EquipmentIdentificationList(pluginInterface, language, dataManager);
|
||||
|
||||
_modelIdentifierToModelChara = new ModelIdentificationList(pluginInterface, language, dataManager);
|
||||
BnpcNames = TryCatchData("BNpcNames", NpcNames.CreateNames);
|
||||
ModelCharaToObjects = TryCatchData("ModelObjects", () => CreateModelObjects(_actorData, dataManager, language));
|
||||
}
|
||||
|
||||
public void Identify(IDictionary<string, object?> set, string path)
|
||||
{
|
||||
|
|
@ -38,48 +66,25 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
return ret;
|
||||
}
|
||||
|
||||
public IReadOnlyList<Item> Identify(SetId setId, WeaponType weaponType, ushort variant, EquipSlot slot)
|
||||
{
|
||||
switch (slot)
|
||||
public IEnumerable<Item> Identify(SetId setId, WeaponType weaponType, ushort variant, EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
case EquipSlot.OffHand:
|
||||
{
|
||||
var (begin, _) = FindIndexRange((List<(ulong, IReadOnlyList<Item>)>)_weapons,
|
||||
(ulong)setId << 32 | (ulong)weaponType << 16 | variant,
|
||||
0xFFFFFFFFFFFF);
|
||||
return begin >= 0 ? _weapons[begin].Item2 : Array.Empty<Item>();
|
||||
}
|
||||
default:
|
||||
{
|
||||
var (begin, _) = FindIndexRange((List<(ulong, IReadOnlyList<Item>)>)_equipment,
|
||||
(ulong)setId << 32 | (ulong)slot.ToSlot() << 16 | variant,
|
||||
0xFFFFFFFFFFFF);
|
||||
return begin >= 0 ? _equipment[begin].Item2 : Array.Empty<Item>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IReadOnlyList<(ulong Key, IReadOnlyList<Item> Values)> _weapons;
|
||||
private readonly IReadOnlyList<(ulong Key, IReadOnlyList<Item> Values)> _equipment;
|
||||
private readonly IReadOnlyList<(ulong Key, IReadOnlyList<(ObjectKind Kind, uint Id)>)> _models;
|
||||
private readonly IReadOnlyDictionary<string, IReadOnlyList<Action>> _actions;
|
||||
|
||||
public ObjectIdentification(DalamudPluginInterface pluginInterface, DataManager dataManager, ClientLanguage language)
|
||||
: base(pluginInterface, language, 1)
|
||||
{
|
||||
_weapons = TryCatchData("Weapons", () => CreateWeaponList(dataManager));
|
||||
_equipment = TryCatchData("Equipment", () => CreateEquipmentList(dataManager));
|
||||
_actions = TryCatchData("Actions", () => CreateActionList(dataManager));
|
||||
_models = TryCatchData("Models", () => CreateModelList(dataManager));
|
||||
}
|
||||
EquipSlot.MainHand => _weapons.Between(setId, weaponType, (byte)variant),
|
||||
EquipSlot.OffHand => _weapons.Between(setId, weaponType, (byte)variant),
|
||||
_ => _equipment.Between(setId, slot, (byte)variant),
|
||||
};
|
||||
|
||||
protected override void DisposeInternal()
|
||||
{
|
||||
DisposeTag("Weapons");
|
||||
DisposeTag("Equipment");
|
||||
_actorData.Dispose();
|
||||
_weapons.Dispose(PluginInterface, Language);
|
||||
_equipment.Dispose(PluginInterface, Language);
|
||||
DisposeTag("Actions");
|
||||
DisposeTag("Models");
|
||||
|
||||
_modelIdentifierToModelChara.Dispose(PluginInterface, Language);
|
||||
DisposeTag("BNpcNames");
|
||||
DisposeTag("ModelObjects");
|
||||
}
|
||||
|
||||
private static bool Add(IDictionary<ulong, HashSet<Item>> dict, ulong key, Item item)
|
||||
|
|
@ -93,25 +98,25 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
|
||||
private static ulong EquipmentKey(Item i)
|
||||
{
|
||||
var model = (ulong)((Lumina.Data.Parsing.Quad)i.ModelMain).A;
|
||||
var model = (ulong)((Lumina.Data.Parsing.Quad)i.ModelMain).A;
|
||||
var variant = (ulong)((Lumina.Data.Parsing.Quad)i.ModelMain).B;
|
||||
var slot = (ulong)((EquipSlot)i.EquipSlotCategory.Row).ToSlot();
|
||||
return model << 32 | slot << 16 | variant;
|
||||
var slot = (ulong)((EquipSlot)i.EquipSlotCategory.Row).ToSlot();
|
||||
return (model << 32) | (slot << 16) | variant;
|
||||
}
|
||||
|
||||
private static ulong WeaponKey(Item i, bool offhand)
|
||||
{
|
||||
var quad = offhand ? (Lumina.Data.Parsing.Quad)i.ModelSub : (Lumina.Data.Parsing.Quad)i.ModelMain;
|
||||
var model = (ulong)quad.A;
|
||||
var type = (ulong)quad.B;
|
||||
var quad = offhand ? (Lumina.Data.Parsing.Quad)i.ModelSub : (Lumina.Data.Parsing.Quad)i.ModelMain;
|
||||
var model = (ulong)quad.A;
|
||||
var type = (ulong)quad.B;
|
||||
var variant = (ulong)quad.C;
|
||||
|
||||
return model << 32 | type << 16 | variant;
|
||||
return (model << 32) | (type << 16) | variant;
|
||||
}
|
||||
|
||||
private IReadOnlyList<(ulong Key, IReadOnlyList<Item> Values)> CreateWeaponList(DataManager gameData)
|
||||
{
|
||||
var items = gameData.GetExcelSheet<Item>(Language)!;
|
||||
var items = gameData.GetExcelSheet<Item>(Language)!;
|
||||
var storage = new SortedList<ulong, HashSet<Item>>();
|
||||
foreach (var item in items.Where(i
|
||||
=> (EquipSlot)i.EquipSlotCategory.Row is EquipSlot.MainHand or EquipSlot.OffHand or EquipSlot.BothHand))
|
||||
|
|
@ -128,7 +133,7 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
|
||||
private IReadOnlyList<(ulong Key, IReadOnlyList<Item> Values)> CreateEquipmentList(DataManager gameData)
|
||||
{
|
||||
var items = gameData.GetExcelSheet<Item>(Language)!;
|
||||
var items = gameData.GetExcelSheet<Item>(Language)!;
|
||||
var storage = new SortedList<ulong, HashSet<Item>>();
|
||||
foreach (var item in items)
|
||||
{
|
||||
|
|
@ -162,7 +167,7 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
|
||||
private IReadOnlyDictionary<string, IReadOnlyList<Action>> CreateActionList(DataManager gameData)
|
||||
{
|
||||
var sheet = gameData.GetExcelSheet<Action>(Language)!;
|
||||
var sheet = gameData.GetExcelSheet<Action>(Language)!;
|
||||
var storage = new Dictionary<string, HashSet<Action>>((int)sheet.RowCount);
|
||||
|
||||
void AddAction(string? key, Action action)
|
||||
|
|
@ -180,51 +185,29 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
foreach (var action in sheet.Where(a => !a.Name.RawData.IsEmpty))
|
||||
{
|
||||
var startKey = action.AnimationStart?.Value?.Name?.Value?.Key.ToDalamudString().ToString();
|
||||
var endKey = action.AnimationEnd?.Value?.Key.ToDalamudString().ToString();
|
||||
var hitKey = action.ActionTimelineHit?.Value?.Key.ToDalamudString().ToString();
|
||||
var endKey = action.AnimationEnd?.Value?.Key.ToDalamudString().ToString();
|
||||
var hitKey = action.ActionTimelineHit?.Value?.Key.ToDalamudString().ToString();
|
||||
AddAction(startKey, action);
|
||||
AddAction(endKey, action);
|
||||
AddAction(hitKey, action);
|
||||
AddAction(endKey, action);
|
||||
AddAction(hitKey, action);
|
||||
}
|
||||
|
||||
return storage.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<Action>)kvp.Value.ToArray());
|
||||
}
|
||||
|
||||
private static ulong ModelValue(ModelChara row)
|
||||
=> row.Type | (ulong)row.Model << 8 | (ulong)row.Base << 24 | (ulong)row.Variant << 32;
|
||||
|
||||
private static IEnumerable<(ulong, ObjectKind, uint)> BattleNpcToName(ulong model, uint bNpc)
|
||||
=> Enumerable.Repeat((model, ObjectKind.BattleNpc, bNpc), 1);
|
||||
|
||||
private IReadOnlyList<(ulong Key, IReadOnlyList<(ObjectKind Kind, uint Id)>)> CreateModelList(DataManager gameData)
|
||||
{
|
||||
var sheetBNpc = gameData.GetExcelSheet<BNpcBase>(Language)!;
|
||||
var sheetENpc = gameData.GetExcelSheet<ENpcBase>(Language)!;
|
||||
var sheetCompanion = gameData.GetExcelSheet<Companion>(Language)!;
|
||||
var sheetMount = gameData.GetExcelSheet<Mount>(Language)!;
|
||||
var sheetModel = gameData.GetExcelSheet<ModelChara>(Language)!;
|
||||
|
||||
var modelCharaToModel = sheetModel.ToDictionary(m => m.RowId, ModelValue);
|
||||
|
||||
return sheetENpc.Select(e => (modelCharaToModel[e.ModelChara.Row], ObjectKind.EventNpc, e.RowId))
|
||||
.Concat(sheetCompanion.Select(c => (modelCharaToModel[c.Model.Row], ObjectKind.Companion, c.RowId)))
|
||||
.Concat(sheetMount.Select(c => (modelCharaToModel[c.ModelChara.Row], ObjectKind.MountType, c.RowId)))
|
||||
.Concat(sheetBNpc.SelectMany(c => BattleNpcToName(modelCharaToModel[c.ModelChara.Row], c.RowId)))
|
||||
.GroupBy(t => t.Item1)
|
||||
.Select(g => (g.Key, (IReadOnlyList<(ObjectKind, uint)>)g.Select(p => (p.Item2, p.Item3)).ToArray()))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private class Comparer : IComparer<(ulong, IReadOnlyList<Item>)>
|
||||
{
|
||||
public int Compare((ulong, IReadOnlyList<Item>) x, (ulong, IReadOnlyList<Item>) y)
|
||||
=> x.Item1.CompareTo(y.Item1);
|
||||
}
|
||||
|
||||
private static readonly Comparer _arrayComparer = new();
|
||||
|
||||
|
||||
private static (int, int) FindIndexRange(List<(ulong, IReadOnlyList<Item>)> list, ulong key, ulong mask)
|
||||
{
|
||||
var maskedKey = key & mask;
|
||||
var idx = list.BinarySearch(0, list.Count, (key, null!), new Comparer());
|
||||
var idx = list.BinarySearch(0, list.Count, (key, null!), _arrayComparer);
|
||||
if (idx < 0)
|
||||
{
|
||||
if (~idx == list.Count || maskedKey != (list[~idx].Item1 & mask))
|
||||
|
|
@ -242,55 +225,30 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
|
||||
private void FindEquipment(IDictionary<string, object?> set, GameObjectInfo info)
|
||||
{
|
||||
var key = (ulong)info.PrimaryId << 32;
|
||||
var mask = 0xFFFF00000000ul;
|
||||
if (info.EquipSlot != EquipSlot.Unknown)
|
||||
{
|
||||
key |= (ulong)info.EquipSlot.ToSlot() << 16;
|
||||
mask |= 0xFFFF0000;
|
||||
}
|
||||
|
||||
if (info.Variant != 0)
|
||||
{
|
||||
key |= info.Variant;
|
||||
mask |= 0xFFFF;
|
||||
}
|
||||
|
||||
var (start, end) = FindIndexRange((List<(ulong, IReadOnlyList<Item>)>)_equipment, key, mask);
|
||||
if (start == -1)
|
||||
return;
|
||||
|
||||
for (; start < end; ++start)
|
||||
{
|
||||
foreach (var item in _equipment[start].Item2)
|
||||
set[item.Name.ToString()] = item;
|
||||
}
|
||||
var items = _equipment.Between(info.PrimaryId, info.EquipSlot, info.Variant);
|
||||
foreach (var item in items)
|
||||
set[item.Name.ToString()] = item;
|
||||
}
|
||||
|
||||
private void FindWeapon(IDictionary<string, object?> set, GameObjectInfo info)
|
||||
{
|
||||
var key = (ulong)info.PrimaryId << 32;
|
||||
var mask = 0xFFFF00000000ul;
|
||||
if (info.SecondaryId != 0)
|
||||
{
|
||||
key |= (ulong)info.SecondaryId << 16;
|
||||
mask |= 0xFFFF0000;
|
||||
}
|
||||
var items = _weapons.Between(info.PrimaryId, info.SecondaryId, info.Variant);
|
||||
foreach (var item in items)
|
||||
set[item.Name.ToString()] = item;
|
||||
}
|
||||
|
||||
if (info.Variant != 0)
|
||||
{
|
||||
key |= info.Variant;
|
||||
mask |= 0xFFFF;
|
||||
}
|
||||
|
||||
var (start, end) = FindIndexRange((List<(ulong, IReadOnlyList<Item>)>)_weapons, key, mask);
|
||||
if (start == -1)
|
||||
private void FindModel(IDictionary<string, object?> set, GameObjectInfo info)
|
||||
{
|
||||
var type = info.ObjectType.ToModelType();
|
||||
if (type is 0 or CharacterBase.ModelType.Weapon)
|
||||
return;
|
||||
|
||||
for (; start < end; ++start)
|
||||
var models = _modelIdentifierToModelChara.Between(type, info.PrimaryId, (byte)info.SecondaryId, info.Variant);
|
||||
foreach (var model in models.Where(m => m.RowId < ModelCharaToObjects.Count))
|
||||
{
|
||||
foreach (var item in _weapons[start].Item2)
|
||||
set[item.Name.ToString()] = item;
|
||||
var objectList = ModelCharaToObjects[(int)model.RowId];
|
||||
foreach (var (name, kind) in objectList)
|
||||
set[$"{name} ({kind.ToName()})"] = model;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -332,10 +290,10 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
AddCounterString(set, info.ObjectType.ToString());
|
||||
break;
|
||||
case ObjectType.DemiHuman:
|
||||
set[$"Demi Human: {info.PrimaryId}"] = null;
|
||||
FindModel(set, info);
|
||||
break;
|
||||
case ObjectType.Monster:
|
||||
set[$"Monster: {info.PrimaryId}"] = null;
|
||||
FindModel(set, info);
|
||||
break;
|
||||
case ObjectType.Icon:
|
||||
set[$"Icon: {info.IconId}"] = null;
|
||||
|
|
@ -349,7 +307,7 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
break;
|
||||
case ObjectType.Character:
|
||||
var (gender, race) = info.GenderRace.Split();
|
||||
var raceString = race != ModelRace.Unknown ? race.ToName() + " " : "";
|
||||
var raceString = race != ModelRace.Unknown ? race.ToName() + " " : "";
|
||||
var genderString = gender != Gender.Unknown ? gender.ToName() + " " : "Player ";
|
||||
switch (info.CustomizationType)
|
||||
{
|
||||
|
|
@ -365,16 +323,16 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
case CustomizationType.DecalEquip:
|
||||
set[$"Equipment Decal {info.PrimaryId}"] = null;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
var customizationString = race == ModelRace.Unknown
|
||||
|| info.BodySlot == BodySlot.Unknown
|
||||
|| info.CustomizationType == CustomizationType.Unknown
|
||||
? "Customization: Unknown"
|
||||
: $"Customization: {race} {gender} {info.BodySlot} ({info.CustomizationType}) {info.PrimaryId}";
|
||||
set[customizationString] = null;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
var customizationString = race == ModelRace.Unknown
|
||||
|| info.BodySlot == BodySlot.Unknown
|
||||
|| info.CustomizationType == CustomizationType.Unknown
|
||||
? "Customization: Unknown"
|
||||
: $"Customization: {race} {gender} {info.BodySlot} ({info.CustomizationType}) {info.PrimaryId}";
|
||||
set[customizationString] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -386,10 +344,74 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
private void IdentifyVfx(IDictionary<string, object?> set, string path)
|
||||
{
|
||||
var key = GamePathParser.VfxToKey(path);
|
||||
if (key.Length == 0 || !_actions.TryGetValue(key, out var actions))
|
||||
if (key.Length == 0 || !Actions.TryGetValue(key, out var actions))
|
||||
return;
|
||||
|
||||
foreach (var action in actions)
|
||||
set[$"Action: {action.Name}"] = action;
|
||||
}
|
||||
|
||||
private IReadOnlyList<IReadOnlyList<(string Name, ObjectKind Kind)>> CreateModelObjects(ActorManager.ActorManagerData actors,
|
||||
DataManager gameData,
|
||||
ClientLanguage language)
|
||||
{
|
||||
var modelSheet = gameData.GetExcelSheet<ModelChara>(language)!;
|
||||
var bnpcSheet = gameData.GetExcelSheet<BNpcBase>(language)!;
|
||||
var enpcSheet = gameData.GetExcelSheet<ENpcBase>(language)!;
|
||||
var ornamentSheet = gameData.GetExcelSheet<Ornament>(language)!;
|
||||
var mountSheet = gameData.GetExcelSheet<Mount>(language)!;
|
||||
var companionSheet = gameData.GetExcelSheet<Companion>(language)!;
|
||||
var ret = new List<HashSet<(string Name, ObjectKind Kind)>>((int)modelSheet.RowCount);
|
||||
|
||||
for (var i = -1; i < modelSheet.Last().RowId; ++i)
|
||||
ret.Add(new HashSet<(string Name, ObjectKind Kind)>());
|
||||
|
||||
void Add(int modelChara, ObjectKind kind, uint dataId)
|
||||
{
|
||||
if (modelChara == 0 || modelChara >= ret.Count)
|
||||
return;
|
||||
|
||||
if (actors.TryGetName(kind, dataId, out var name))
|
||||
ret[modelChara].Add((name, kind));
|
||||
}
|
||||
|
||||
foreach (var ornament in ornamentSheet)
|
||||
Add(ornament.Model, (ObjectKind)15, ornament.RowId);
|
||||
|
||||
foreach (var mount in mountSheet)
|
||||
Add((int)mount.ModelChara.Row, ObjectKind.MountType, mount.RowId);
|
||||
|
||||
foreach (var companion in companionSheet)
|
||||
Add((int)companion.Model.Row, ObjectKind.Companion, companion.RowId);
|
||||
|
||||
foreach (var enpc in enpcSheet)
|
||||
Add((int)enpc.ModelChara.Row, ObjectKind.EventNpc, enpc.RowId);
|
||||
|
||||
foreach (var bnpc in bnpcSheet.Where(b => b.RowId < BnpcNames.Count))
|
||||
{
|
||||
foreach (var name in BnpcNames[(int)bnpc.RowId])
|
||||
Add((int)bnpc.ModelChara.Row, ObjectKind.BattleNpc, name);
|
||||
}
|
||||
|
||||
return ret.Select(s => s.Count > 0
|
||||
? s.ToArray()
|
||||
: Array.Empty<(string Name, ObjectKind Kind)>()).ToArray();
|
||||
}
|
||||
|
||||
public static unsafe ulong KeyFromCharacterBase(CharacterBase* drawObject)
|
||||
{
|
||||
var type = (*(delegate* unmanaged<CharacterBase*, uint>**)drawObject)[50](drawObject);
|
||||
var unk = (ulong)*((byte*)drawObject + 0x8E8) << 8;
|
||||
return type switch
|
||||
{
|
||||
1 => type | unk,
|
||||
2 => type | unk | ((ulong)*(ushort*)((byte*)drawObject + 0x908) << 16),
|
||||
3 => type
|
||||
| unk
|
||||
| ((ulong)*(ushort*)((byte*)drawObject + 0x8F0) << 16)
|
||||
| ((ulong)**(ushort**)((byte*)drawObject + 0x910) << 32)
|
||||
| ((ulong)**(ushort**)((byte*)drawObject + 0x910) << 40),
|
||||
_ => 0u,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
464
Penumbra.GameData/Data/RestrictedGear.cs
Normal file
464
Penumbra.GameData/Data/RestrictedGear.cs
Normal file
|
|
@ -0,0 +1,464 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Utility;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Race = Penumbra.GameData.Enums.Race;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
/// <summary>
|
||||
/// Handle gender- or race-locked gear in the draw model itself.
|
||||
/// Racial gear gets swapped to the correct current race and gender (it is one set each).
|
||||
/// Gender-locked gear gets swapped to the equivalent set if it exists (most of them do),
|
||||
/// with some items getting send to emperor's new clothes and a few funny entries.
|
||||
/// </summary>
|
||||
public sealed class RestrictedGear : DataSharer
|
||||
{
|
||||
private readonly ExcelSheet<Item> _items;
|
||||
private readonly ExcelSheet<EquipRaceCategory> _categories;
|
||||
|
||||
public readonly IReadOnlySet<uint> RaceGenderSet;
|
||||
public readonly IReadOnlyDictionary<uint, uint> MaleToFemale;
|
||||
public readonly IReadOnlyDictionary<uint, uint> FemaleToMale;
|
||||
|
||||
internal RestrictedGear(DalamudPluginInterface pi, ClientLanguage language, DataManager gameData)
|
||||
: base(pi, language, 1)
|
||||
{
|
||||
_items = gameData.GetExcelSheet<Item>()!;
|
||||
_categories = gameData.GetExcelSheet<EquipRaceCategory>()!;
|
||||
(RaceGenderSet, MaleToFemale, FemaleToMale) = TryCatchData("RestrictedGear", CreateRestrictedGear);
|
||||
}
|
||||
|
||||
protected override void DisposeInternal()
|
||||
=> DisposeTag("RestrictedGear");
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a model given by its model id, variant and slot for your current race and gender.
|
||||
/// </summary>
|
||||
/// <param name="armor">The equipment piece.</param>
|
||||
/// <param name="slot">The equipment slot.</param>
|
||||
/// <param name="race">The intended race.</param>
|
||||
/// <param name="gender">The intended gender.</param>
|
||||
/// <returns>True and the changed-to piece of gear or false and the same piece of gear.</returns>
|
||||
public (bool Replaced, CharacterArmor Armor) ResolveRestricted(CharacterArmor armor, EquipSlot slot, Race race, Gender gender)
|
||||
{
|
||||
var quad = armor.Set.Value | ((uint)armor.Variant << 16);
|
||||
// Check racial gear, this does not need slots.
|
||||
if (RaceGenderGroup.Contains(quad))
|
||||
{
|
||||
var idx = ((int)race - 1) * 2 + (gender is Gender.Female or Gender.FemaleNpc ? 1 : 0);
|
||||
var value = RaceGenderGroup[idx];
|
||||
return (value != quad, new CharacterArmor((ushort)value, (byte)(value >> 16), armor.Stain));
|
||||
}
|
||||
|
||||
// Check gender slots. If current gender is female, check if anything needs to be changed from male to female,
|
||||
// and vice versa.
|
||||
// Some items lead to the exact same model- and variant id just gender specified,
|
||||
// so check for actual difference in the Replaced bool.
|
||||
var needle = quad | ((uint)slot.ToSlot() << 24);
|
||||
if (gender is Gender.Female or Gender.FemaleNpc && MaleToFemale.TryGetValue(needle, out var newValue)
|
||||
|| gender is Gender.Male or Gender.MaleNpc && FemaleToMale.TryGetValue(needle, out newValue))
|
||||
return (quad != newValue, new CharacterArmor((ushort)newValue, (byte)(newValue >> 16), armor.Stain));
|
||||
|
||||
// The gear is not restricted.
|
||||
return (false, armor);
|
||||
}
|
||||
|
||||
private Tuple<IReadOnlySet<uint>, IReadOnlyDictionary<uint, uint>, IReadOnlyDictionary<uint, uint>> CreateRestrictedGear()
|
||||
{
|
||||
var m2f = new Dictionary<uint, uint>();
|
||||
var f2m = new Dictionary<uint, uint>();
|
||||
var rg = RaceGenderGroup.Where(c => c is not 0 and not uint.MaxValue).ToHashSet();
|
||||
AddKnown(m2f, f2m);
|
||||
UnhandledRestrictedGear(m2f, f2m, false); // Set this to true to create a print of unassigned gear on launch.
|
||||
return new Tuple<IReadOnlySet<uint>, IReadOnlyDictionary<uint, uint>, IReadOnlyDictionary<uint, uint>>(rg, m2f, f2m);
|
||||
}
|
||||
|
||||
|
||||
// Add all unknown restricted gear and pair it with emperor's new gear on start up.
|
||||
// Can also print unhandled items.
|
||||
private void UnhandledRestrictedGear(Dictionary<uint, uint> m2f, Dictionary<uint, uint> f2m, bool print)
|
||||
{
|
||||
if (print)
|
||||
PluginLog.Information("#### MALE ONLY ######");
|
||||
|
||||
void AddEmperor(Item item, bool male, bool female)
|
||||
{
|
||||
var slot = ((EquipSlot)item.EquipSlotCategory.Row).ToSlot();
|
||||
var emperor = slot switch
|
||||
{
|
||||
EquipSlot.Head => 10032u,
|
||||
EquipSlot.Body => 10033u,
|
||||
EquipSlot.Hands => 10034u,
|
||||
EquipSlot.Legs => 10035u,
|
||||
EquipSlot.Feet => 10036u,
|
||||
EquipSlot.Ears => 09293u,
|
||||
EquipSlot.Neck => 09292u,
|
||||
EquipSlot.Wrists => 09294u,
|
||||
EquipSlot.RFinger => 09295u,
|
||||
EquipSlot.LFinger => 09295u,
|
||||
_ => 0u,
|
||||
};
|
||||
if (emperor == 0)
|
||||
return;
|
||||
|
||||
if (male)
|
||||
AddItem(m2f, f2m, item.RowId, emperor, true, false);
|
||||
if (female)
|
||||
AddItem(m2f, f2m, emperor, item.RowId, false, true);
|
||||
}
|
||||
|
||||
var unhandled = 0;
|
||||
foreach (var item in _items.Where(i => i.EquipRestriction == 2))
|
||||
{
|
||||
if (m2f.ContainsKey((uint)item.ModelMain | ((uint)((EquipSlot)item.EquipSlotCategory.Row).ToSlot() << 24)))
|
||||
continue;
|
||||
|
||||
++unhandled;
|
||||
AddEmperor(item, true, false);
|
||||
|
||||
if (print)
|
||||
PluginLog.Information($"{item.RowId:D5} {item.Name.ToDalamudString().TextValue}");
|
||||
}
|
||||
|
||||
if (print)
|
||||
PluginLog.Information("#### FEMALE ONLY ####");
|
||||
foreach (var item in _items.Where(i => i.EquipRestriction == 3))
|
||||
{
|
||||
if (f2m.ContainsKey((uint)item.ModelMain | ((uint)((EquipSlot)item.EquipSlotCategory.Row).ToSlot() << 24)))
|
||||
continue;
|
||||
|
||||
++unhandled;
|
||||
AddEmperor(item, false, true);
|
||||
|
||||
if (print)
|
||||
PluginLog.Information($"{item.RowId:D5} {item.Name.ToDalamudString().TextValue}");
|
||||
}
|
||||
|
||||
if (print)
|
||||
PluginLog.Information("#### OTHER #########");
|
||||
|
||||
foreach (var item in _items.Where(i => i.EquipRestriction > 3))
|
||||
{
|
||||
if (RaceGenderSet.Contains((uint)item.ModelMain))
|
||||
continue;
|
||||
|
||||
++unhandled;
|
||||
if (print)
|
||||
PluginLog.Information(
|
||||
$"{item.RowId:D5} {item.Name.ToDalamudString().TextValue} RestrictionGroup {_categories.GetRow(item.EquipRestriction)!.RowId:D2}");
|
||||
}
|
||||
|
||||
if (unhandled > 0)
|
||||
PluginLog.Warning($"There were {unhandled} restricted items not handled and directed to Emperor's New Set.");
|
||||
}
|
||||
|
||||
// Add a item redirection by its item - NOT MODEL - id.
|
||||
// This uses the items model as well as its slot.
|
||||
// Creates a <-> redirection by default but can add -> or <- redirections by setting the corresponding bools to false.
|
||||
// Prints warnings if anything does not make sense.
|
||||
private void AddItem(Dictionary<uint, uint> m2f, Dictionary<uint, uint> f2m, uint itemIdMale, uint itemIdFemale, bool addMale = true,
|
||||
bool addFemale = true)
|
||||
{
|
||||
if (!addMale && !addFemale)
|
||||
return;
|
||||
|
||||
var mItem = _items.GetRow(itemIdMale);
|
||||
var fItem = _items.GetRow(itemIdFemale);
|
||||
if (mItem == null || fItem == null)
|
||||
{
|
||||
PluginLog.Warning($"Could not add item {itemIdMale} or {itemIdFemale} to restricted items.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mItem.EquipRestriction != 2 && addMale)
|
||||
{
|
||||
PluginLog.Warning($"{mItem.Name.ToDalamudString().TextValue} is not restricted anymore.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (fItem.EquipRestriction != 3 && addFemale)
|
||||
{
|
||||
PluginLog.Warning($"{fItem.Name.ToDalamudString().TextValue} is not restricted anymore.");
|
||||
return;
|
||||
}
|
||||
|
||||
var mSlot = ((EquipSlot)mItem.EquipSlotCategory.Row).ToSlot();
|
||||
var fSlot = ((EquipSlot)fItem.EquipSlotCategory.Row).ToSlot();
|
||||
if (!mSlot.IsAccessory() && !mSlot.IsEquipment())
|
||||
{
|
||||
PluginLog.Warning($"{mItem.Name.ToDalamudString().TextValue} is not equippable to a known slot.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSlot != fSlot)
|
||||
{
|
||||
PluginLog.Warning($"{mItem.Name.ToDalamudString().TextValue} and {fItem.Name.ToDalamudString().TextValue} are not compatible.");
|
||||
return;
|
||||
}
|
||||
|
||||
var mModelIdSlot = (uint)mItem.ModelMain | ((uint)mSlot << 24);
|
||||
var fModelIdSlot = (uint)fItem.ModelMain | ((uint)fSlot << 24);
|
||||
|
||||
if (addMale)
|
||||
m2f.TryAdd(mModelIdSlot, fModelIdSlot);
|
||||
if (addFemale)
|
||||
f2m.TryAdd(fModelIdSlot, mModelIdSlot);
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
// Add all currently existing and known gender restricted items.
|
||||
private void AddKnown(Dictionary<uint, uint> m2f, Dictionary<uint, uint> f2m)
|
||||
{
|
||||
AddItem(m2f, f2m, 02967, 02970); // Lord's Yukata (Blue) <-> Lady's Yukata (Red)
|
||||
AddItem(m2f, f2m, 02968, 02971); // Lord's Yukata (Green) <-> Lady's Yukata (Blue)
|
||||
AddItem(m2f, f2m, 02969, 02972); // Lord's Yukata (Grey) <-> Lady's Yukata (Black)
|
||||
AddItem(m2f, f2m, 02973, 02978); // Red Summer Top <-> Red Summer Halter
|
||||
AddItem(m2f, f2m, 02974, 02979); // Green Summer Top <-> Green Summer Halter
|
||||
AddItem(m2f, f2m, 02975, 02980); // Blue Summer Top <-> Blue Summer Halter
|
||||
AddItem(m2f, f2m, 02976, 02981); // Solar Summer Top <-> Solar Summer Halter
|
||||
AddItem(m2f, f2m, 02977, 02982); // Lunar Summer Top <-> Lunar Summer Halter
|
||||
AddItem(m2f, f2m, 02996, 02997); // Hempen Undershirt <-> Hempen Camise
|
||||
AddItem(m2f, f2m, 03280, 03283); // Lord's Drawers (Black) <-> Lady's Knickers (Black)
|
||||
AddItem(m2f, f2m, 03281, 03284); // Lord's Drawers (White) <-> Lady's Knickers (White)
|
||||
AddItem(m2f, f2m, 03282, 03285); // Lord's Drawers (Gold) <-> Lady's Knickers (Gold)
|
||||
AddItem(m2f, f2m, 03286, 03291); // Red Summer Trunks <-> Red Summer Tanga
|
||||
AddItem(m2f, f2m, 03287, 03292); // Green Summer Trunks <-> Green Summer Tanga
|
||||
AddItem(m2f, f2m, 03288, 03293); // Blue Summer Trunks <-> Blue Summer Tanga
|
||||
AddItem(m2f, f2m, 03289, 03294); // Solar Summer Trunks <-> Solar Summer Tanga
|
||||
AddItem(m2f, f2m, 03290, 03295); // Lunar Summer Trunks <-> Lunar Summer Tanga
|
||||
AddItem(m2f, f2m, 03307, 03308); // Hempen Underpants <-> Hempen Pantalettes
|
||||
AddItem(m2f, f2m, 03748, 03749); // Lord's Clogs <-> Lady's Clogs
|
||||
AddItem(m2f, f2m, 06045, 06041); // Bohemian's Coat <-> Guardian Corps Coat
|
||||
AddItem(m2f, f2m, 06046, 06042); // Bohemian's Gloves <-> Guardian Corps Gauntlets
|
||||
AddItem(m2f, f2m, 06047, 06043); // Bohemian's Trousers <-> Guardian Corps Skirt
|
||||
AddItem(m2f, f2m, 06048, 06044); // Bohemian's Boots <-> Guardian Corps Boots
|
||||
AddItem(m2f, f2m, 06094, 06098); // Summer Evening Top <-> Summer Morning Halter
|
||||
AddItem(m2f, f2m, 06095, 06099); // Summer Evening Trunks <-> Summer Morning Tanga
|
||||
AddItem(m2f, f2m, 06096, 06100); // Striped Summer Top <-> Striped Summer Halter
|
||||
AddItem(m2f, f2m, 06097, 06101); // Striped Summer Trunks <-> Striped Summer Tanga
|
||||
AddItem(m2f, f2m, 06102, 06104); // Black Summer Top <-> Black Summer Halter
|
||||
AddItem(m2f, f2m, 06103, 06105); // Black Summer Trunks <-> Black Summer Tanga
|
||||
AddItem(m2f, f2m, 06972, 06973); // Valentione Apron <-> Valentione Apron Dress
|
||||
AddItem(m2f, f2m, 06975, 06976); // Valentione Trousers <-> Valentione Skirt
|
||||
AddItem(m2f, f2m, 08532, 08535); // Lord's Yukata (Blackflame) <-> Lady's Yukata (Redfly)
|
||||
AddItem(m2f, f2m, 08533, 08536); // Lord's Yukata (Whiteflame) <-> Lady's Yukata (Bluefly)
|
||||
AddItem(m2f, f2m, 08534, 08537); // Lord's Yukata (Blueflame) <-> Lady's Yukata (Pinkfly)
|
||||
AddItem(m2f, f2m, 08542, 08549); // Ti Leaf Lei <-> Coronal Summer Halter
|
||||
AddItem(m2f, f2m, 08543, 08550); // Red Summer Maro <-> Red Summer Pareo
|
||||
AddItem(m2f, f2m, 08544, 08551); // South Seas Talisman <-> Sea Breeze Summer Halter
|
||||
AddItem(m2f, f2m, 08545, 08552); // Blue Summer Maro <-> Sea Breeze Summer Pareo
|
||||
AddItem(m2f, f2m, 08546, 08553); // Coeurl Talisman <-> Coeurl Beach Halter
|
||||
AddItem(m2f, f2m, 08547, 08554); // Coeurl Beach Maro <-> Coeurl Beach Pareo
|
||||
AddItem(m2f, f2m, 08548, 08555); // Coeurl Beach Briefs <-> Coeurl Beach Tanga
|
||||
AddItem(m2f, f2m, 10316, 10317); // Southern Seas Vest <-> Southern Seas Swimsuit
|
||||
AddItem(m2f, f2m, 10318, 10319); // Southern Seas Trunks <-> Southern Seas Tanga
|
||||
AddItem(m2f, f2m, 10320, 10321); // Striped Southern Seas Vest <-> Striped Southern Seas Swimsuit
|
||||
AddItem(m2f, f2m, 13298, 13567); // Black-feathered Flat Hat <-> Red-feathered Flat Hat
|
||||
AddItem(m2f, f2m, 13300, 13639); // Lord's Suikan <-> Lady's Suikan
|
||||
AddItem(m2f, f2m, 13724, 13725); // Little Lord's Clogs <-> Little Lady's Clogs
|
||||
AddItem(m2f, f2m, 14854, 14857); // Eastern Lord's Togi <-> Eastern Lady's Togi
|
||||
AddItem(m2f, f2m, 14855, 14858); // Eastern Lord's Trousers <-> Eastern Lady's Loincloth
|
||||
AddItem(m2f, f2m, 14856, 14859); // Eastern Lord's Crakows <-> Eastern Lady's Crakows
|
||||
AddItem(m2f, f2m, 15639, 15642); // Far Eastern Patriarch's Hat <-> Far Eastern Matriarch's Sun Hat
|
||||
AddItem(m2f, f2m, 15640, 15643); // Far Eastern Patriarch's Tunic <-> Far Eastern Matriarch's Dress
|
||||
AddItem(m2f, f2m, 15641, 15644); // Far Eastern Patriarch's Longboots <-> Far Eastern Matriarch's Boots
|
||||
AddItem(m2f, f2m, 15922, 15925); // Moonfire Vest <-> Moonfire Halter
|
||||
AddItem(m2f, f2m, 15923, 15926); // Moonfire Trunks <-> Moonfire Tanga
|
||||
AddItem(m2f, f2m, 15924, 15927); // Moonfire Caligae <-> Moonfire Sandals
|
||||
AddItem(m2f, f2m, 16106, 16111); // Makai Mauler's Facemask <-> Makai Manhandler's Facemask
|
||||
AddItem(m2f, f2m, 16107, 16112); // Makai Mauler's Oilskin <-> Makai Manhandler's Jerkin
|
||||
AddItem(m2f, f2m, 16108, 16113); // Makai Mauler's Fingerless Gloves <-> Makai Manhandler's Fingerless Gloves
|
||||
AddItem(m2f, f2m, 16109, 16114); // Makai Mauler's Leggings <-> Makai Manhandler's Quartertights
|
||||
AddItem(m2f, f2m, 16110, 16115); // Makai Mauler's Boots <-> Makai Manhandler's Longboots
|
||||
AddItem(m2f, f2m, 16116, 16121); // Makai Marksman's Eyepatch <-> Makai Markswoman's Ribbon
|
||||
AddItem(m2f, f2m, 16117, 16122); // Makai Marksman's Battlegarb <-> Makai Markswoman's Battledress
|
||||
AddItem(m2f, f2m, 16118, 16123); // Makai Marksman's Fingerless Gloves <-> Makai Markswoman's Fingerless Gloves
|
||||
AddItem(m2f, f2m, 16119, 16124); // Makai Marksman's Slops <-> Makai Markswoman's Quartertights
|
||||
AddItem(m2f, f2m, 16120, 16125); // Makai Marksman's Boots <-> Makai Markswoman's Longboots
|
||||
AddItem(m2f, f2m, 16126, 16131); // Makai Sun Guide's Circlet <-> Makai Moon Guide's Circlet
|
||||
AddItem(m2f, f2m, 16127, 16132); // Makai Sun Guide's Oilskin <-> Makai Moon Guide's Gown
|
||||
AddItem(m2f, f2m, 16128, 16133); // Makai Sun Guide's Fingerless Gloves <-> Makai Moon Guide's Fingerless Gloves
|
||||
AddItem(m2f, f2m, 16129, 16134); // Makai Sun Guide's Slops <-> Makai Moon Guide's Quartertights
|
||||
AddItem(m2f, f2m, 16130, 16135); // Makai Sun Guide's Boots <-> Makai Moon Guide's Longboots
|
||||
AddItem(m2f, f2m, 16136, 16141); // Makai Priest's Coronet <-> Makai Priestess's Headdress
|
||||
AddItem(m2f, f2m, 16137, 16142); // Makai Priest's Doublet Robe <-> Makai Priestess's Jerkin
|
||||
AddItem(m2f, f2m, 16138, 16143); // Makai Priest's Fingerless Gloves <-> Makai Priestess's Fingerless Gloves
|
||||
AddItem(m2f, f2m, 16139, 16144); // Makai Priest's Slops <-> Makai Priestess's Skirt
|
||||
AddItem(m2f, f2m, 16140, 16145); // Makai Priest's Boots <-> Makai Priestess's Longboots
|
||||
AddItem(m2f, f2m, 16588, 16592); // Far Eastern Gentleman's Hat <-> Far Eastern Beauty's Hairpin
|
||||
AddItem(m2f, f2m, 16589, 16593); // Far Eastern Gentleman's Robe <-> Far Eastern Beauty's Robe
|
||||
AddItem(m2f, f2m, 16590, 16594); // Far Eastern Gentleman's Haidate <-> Far Eastern Beauty's Koshita
|
||||
AddItem(m2f, f2m, 16591, 16595); // Far Eastern Gentleman's Boots <-> Far Eastern Beauty's Boots
|
||||
AddItem(m2f, f2m, 17204, 17209); // Common Makai Mauler's Facemask <-> Common Makai Manhandler's Facemask
|
||||
AddItem(m2f, f2m, 17205, 17210); // Common Makai Mauler's Oilskin <-> Common Makai Manhandler's Jerkin
|
||||
AddItem(m2f, f2m, 17206, 17211); // Common Makai Mauler's Fingerless Gloves <-> Common Makai Manhandler's Fingerless Glove
|
||||
AddItem(m2f, f2m, 17207, 17212); // Common Makai Mauler's Leggings <-> Common Makai Manhandler's Quartertights
|
||||
AddItem(m2f, f2m, 17208, 17213); // Common Makai Mauler's Boots <-> Common Makai Manhandler's Longboots
|
||||
AddItem(m2f, f2m, 17214, 17219); // Common Makai Marksman's Eyepatch <-> Common Makai Markswoman's Ribbon
|
||||
AddItem(m2f, f2m, 17215, 17220); // Common Makai Marksman's Battlegarb <-> Common Makai Markswoman's Battledress
|
||||
AddItem(m2f, f2m, 17216, 17221); // Common Makai Marksman's Fingerless Gloves <-> Common Makai Markswoman's Fingerless Glove
|
||||
AddItem(m2f, f2m, 17217, 17222); // Common Makai Marksman's Slops <-> Common Makai Markswoman's Quartertights
|
||||
AddItem(m2f, f2m, 17218, 17223); // Common Makai Marksman's Boots <-> Common Makai Markswoman's Longboots
|
||||
AddItem(m2f, f2m, 17224, 17229); // Common Makai Sun Guide's Circlet <-> Common Makai Moon Guide's Circlet
|
||||
AddItem(m2f, f2m, 17225, 17230); // Common Makai Sun Guide's Oilskin <-> Common Makai Moon Guide's Gown
|
||||
AddItem(m2f, f2m, 17226, 17231); // Common Makai Sun Guide's Fingerless Gloves <-> Common Makai Moon Guide's Fingerless Glove
|
||||
AddItem(m2f, f2m, 17227, 17232); // Common Makai Sun Guide's Slops <-> Common Makai Moon Guide's Quartertights
|
||||
AddItem(m2f, f2m, 17228, 17233); // Common Makai Sun Guide's Boots <-> Common Makai Moon Guide's Longboots
|
||||
AddItem(m2f, f2m, 17234, 17239); // Common Makai Priest's Coronet <-> Common Makai Priestess's Headdress
|
||||
AddItem(m2f, f2m, 17235, 17240); // Common Makai Priest's Doublet Robe <-> Common Makai Priestess's Jerkin
|
||||
AddItem(m2f, f2m, 17236, 17241); // Common Makai Priest's Fingerless Gloves <-> Common Makai Priestess's Fingerless Gloves
|
||||
AddItem(m2f, f2m, 17237, 17242); // Common Makai Priest's Slops <-> Common Makai Priestess's Skirt
|
||||
AddItem(m2f, f2m, 17238, 17243); // Common Makai Priest's Boots <-> Common Makai Priestess's Longboots
|
||||
AddItem(m2f, f2m, 17481, 17476); // Royal Seneschal's Chapeau <-> Songbird Hat
|
||||
AddItem(m2f, f2m, 17482, 17477); // Royal Seneschal's Coat <-> Songbird Jacket
|
||||
AddItem(m2f, f2m, 17483, 17478); // Royal Seneschal's Fingerless Gloves <-> Songbird Gloves
|
||||
AddItem(m2f, f2m, 17484, 17479); // Royal Seneschal's Breeches <-> Songbird Skirt
|
||||
AddItem(m2f, f2m, 17485, 17480); // Royal Seneschal's Boots <-> Songbird Boots
|
||||
AddItem(m2f, f2m, 20479, 20484); // Star of the Nezha Lord <-> Star of the Nezha Lady
|
||||
AddItem(m2f, f2m, 20480, 20485); // Nezha Lord's Togi <-> Nezha Lady's Togi
|
||||
AddItem(m2f, f2m, 20481, 20486); // Nezha Lord's Gloves <-> Nezha Lady's Gloves
|
||||
AddItem(m2f, f2m, 20482, 20487); // Nezha Lord's Slops <-> Nezha Lady's Slops
|
||||
AddItem(m2f, f2m, 20483, 20488); // Nezha Lord's Boots <-> Nezha Lady's Kneeboots
|
||||
AddItem(m2f, f2m, 22367, 22372); // Faerie Tale Prince's Circlet <-> Faerie Tale Princess's Tiara
|
||||
AddItem(m2f, f2m, 22368, 22373); // Faerie Tale Prince's Vest <-> Faerie Tale Princess's Dress
|
||||
AddItem(m2f, f2m, 22369, 22374); // Faerie Tale Prince's Gloves <-> Faerie Tale Princess's Gloves
|
||||
AddItem(m2f, f2m, 22370, 22375); // Faerie Tale Prince's Slops <-> Faerie Tale Princess's Long Skirt
|
||||
AddItem(m2f, f2m, 22371, 22376); // Faerie Tale Prince's Boots <-> Faerie Tale Princess's Heels
|
||||
AddItem(m2f, f2m, 24599, 24602); // Far Eastern Schoolboy's Hat <-> Far Eastern Schoolgirl's Hair Ribbon
|
||||
AddItem(m2f, f2m, 24600, 24603); // Far Eastern Schoolboy's Hakama <-> Far Eastern Schoolgirl's Hakama
|
||||
AddItem(m2f, f2m, 24601, 24604); // Far Eastern Schoolboy's Zori <-> Far Eastern Schoolgirl's Boots
|
||||
AddItem(m2f, f2m, 28558, 28573); // Valentione Rose Hat <-> Valentione Rose Ribboned Hat
|
||||
AddItem(m2f, f2m, 28559, 28574); // Valentione Rose Waistcoat <-> Valentione Rose Dress
|
||||
AddItem(m2f, f2m, 28560, 28575); // Valentione Rose Gloves <-> Valentione Rose Ribboned Gloves
|
||||
AddItem(m2f, f2m, 28561, 28576); // Valentione Rose Slacks <-> Valentione Rose Tights
|
||||
AddItem(m2f, f2m, 28562, 28577); // Valentione Rose Shoes <-> Valentione Rose Heels
|
||||
AddItem(m2f, f2m, 28563, 28578); // Valentione Forget-me-not Hat <-> Valentione Forget-me-not Ribboned Hat
|
||||
AddItem(m2f, f2m, 28564, 28579); // Valentione Forget-me-not Waistcoat <-> Valentione Forget-me-not Dress
|
||||
AddItem(m2f, f2m, 28565, 28580); // Valentione Forget-me-not Gloves <-> Valentione Forget-me-not Ribboned Gloves
|
||||
AddItem(m2f, f2m, 28566, 28581); // Valentione Forget-me-not Slacks <-> Valentione Forget-me-not Tights
|
||||
AddItem(m2f, f2m, 28567, 28582); // Valentione Forget-me-not Shoes <-> Valentione Forget-me-not Heels
|
||||
AddItem(m2f, f2m, 28568, 28583); // Valentione Acacia Hat <-> Valentione Acacia Ribboned Hat
|
||||
AddItem(m2f, f2m, 28569, 28584); // Valentione Acacia Waistcoat <-> Valentione Acacia Dress
|
||||
AddItem(m2f, f2m, 28570, 28585); // Valentione Acacia Gloves <-> Valentione Acacia Ribboned Gloves
|
||||
AddItem(m2f, f2m, 28571, 28586); // Valentione Acacia Slacks <-> Valentione Acacia Tights
|
||||
AddItem(m2f, f2m, 28572, 28587); // Valentione Acacia Shoes <-> Valentione Acacia Heels
|
||||
AddItem(m2f, f2m, 28600, 28605); // Eastern Lord Errant's Hat <-> Eastern Lady Errant's Hat
|
||||
AddItem(m2f, f2m, 28601, 28606); // Eastern Lord Errant's Jacket <-> Eastern Lady Errant's Coat
|
||||
AddItem(m2f, f2m, 28602, 28607); // Eastern Lord Errant's Wristbands <-> Eastern Lady Errant's Gloves
|
||||
AddItem(m2f, f2m, 28603, 28608); // Eastern Lord Errant's Trousers <-> Eastern Lady Errant's Skirt
|
||||
AddItem(m2f, f2m, 28604, 28609); // Eastern Lord Errant's Shoes <-> Eastern Lady Errant's Boots
|
||||
AddItem(m2f, f2m, 31408, 31413); // Bergsteiger's Hat <-> Dirndl's Hat
|
||||
AddItem(m2f, f2m, 31409, 31414); // Bergsteiger's Jacket <-> Dirndl's Bodice
|
||||
AddItem(m2f, f2m, 31410, 31415); // Bergsteiger's Halfgloves <-> Dirndl's Wrist Torque
|
||||
AddItem(m2f, f2m, 31411, 31416); // Bergsteiger's Halfslops <-> Dirndl's Long Skirt
|
||||
AddItem(m2f, f2m, 31412, 31417); // Bergsteiger's Boots <-> Dirndl's Pumps
|
||||
AddItem(m2f, f2m, 36336, 36337); // Omega-M Attire <-> Omega-F Attire
|
||||
AddItem(m2f, f2m, 36338, 36339); // Omega-M Ear Cuffs <-> Omega-F Earrings
|
||||
AddItem(m2f, f2m, 37442, 37447); // Makai Vanguard's Monocle <-> Makai Vanbreaker's Ribbon
|
||||
AddItem(m2f, f2m, 37443, 37448); // Makai Vanguard's Battlegarb <-> Makai Vanbreaker's Battledress
|
||||
AddItem(m2f, f2m, 37444, 37449); // Makai Vanguard's Fingerless Gloves <-> Makai Vanbreaker's Fingerless Gloves
|
||||
AddItem(m2f, f2m, 37445, 37450); // Makai Vanguard's Leggings <-> Makai Vanbreaker's Quartertights
|
||||
AddItem(m2f, f2m, 37446, 37451); // Makai Vanguard's Boots <-> Makai Vanbreaker's Longboots
|
||||
AddItem(m2f, f2m, 37452, 37457); // Makai Harbinger's Facemask <-> Makai Harrower's Facemask
|
||||
AddItem(m2f, f2m, 37453, 37458); // Makai Harbinger's Battlegarb <-> Makai Harrower's Jerkin
|
||||
AddItem(m2f, f2m, 37454, 37459); // Makai Harbinger's Fingerless Gloves <-> Makai Harrower's Fingerless Gloves
|
||||
AddItem(m2f, f2m, 37455, 37460); // Makai Harbinger's Leggings <-> Makai Harrower's Quartertights
|
||||
AddItem(m2f, f2m, 37456, 37461); // Makai Harbinger's Boots <-> Makai Harrower's Longboots
|
||||
AddItem(m2f, f2m, 37462, 37467); // Common Makai Vanguard's Monocle <-> Common Makai Vanbreaker's Ribbon
|
||||
AddItem(m2f, f2m, 37463, 37468); // Common Makai Vanguard's Battlegarb <-> Common Makai Vanbreaker's Battledress
|
||||
AddItem(m2f, f2m, 37464, 37469); // Common Makai Vanguard's Fingerless Gloves <-> Common Makai Vanbreaker's Fingerless Gloves
|
||||
AddItem(m2f, f2m, 37465, 37470); // Common Makai Vanguard's Leggings <-> Common Makai Vanbreaker's Quartertights
|
||||
AddItem(m2f, f2m, 37466, 37471); // Common Makai Vanguard's Boots <-> Common Makai Vanbreaker's Longboots
|
||||
AddItem(m2f, f2m, 37472, 37477); // Common Makai Harbinger's Facemask <-> Common Makai Harrower's Facemask
|
||||
AddItem(m2f, f2m, 37473, 37478); // Common Makai Harbinger's Battlegarb <-> Common Makai Harrower's Jerkin
|
||||
AddItem(m2f, f2m, 37474, 37479); // Common Makai Harbinger's Fingerless Gloves <-> Common Makai Harrower's Fingerless Gloves
|
||||
AddItem(m2f, f2m, 37475, 37480); // Common Makai Harbinger's Leggings <-> Common Makai Harrower's Quartertights
|
||||
AddItem(m2f, f2m, 37476, 37481); // Common Makai Harbinger's Boots <-> Common Makai Harrower's Longboots
|
||||
AddItem(m2f, f2m, 13323, 13322); // Scion Thief's Tunic <-> Scion Conjurer's Dalmatica
|
||||
AddItem(m2f, f2m, 13693, 10034, true, false); // Scion Thief's Halfgloves -> The Emperor's New Gloves
|
||||
AddItem(m2f, f2m, 13694, 13691); // Scion Thief's Gaskins <-> Scion Conjurer's Chausses
|
||||
AddItem(m2f, f2m, 13695, 13692); // Scion Thief's Armored Caligae <-> Scion Conjurer's Pattens
|
||||
AddItem(m2f, f2m, 13326, 30063); // Scion Thaumaturge's Robe <-> Scion Sorceress's Headdress
|
||||
AddItem(m2f, f2m, 13696, 30062); // Scion Thaumaturge's Monocle <-> Scion Sorceress's Robe
|
||||
AddItem(m2f, f2m, 13697, 30064); // Scion Thaumaturge's Gauntlets <-> Scion Sorceress's Shadowtalons
|
||||
AddItem(m2f, f2m, 13698, 10035, true, false); // Scion Thaumaturge's Gaskins -> The Emperor's New Breeches
|
||||
AddItem(m2f, f2m, 13699, 30065); // Scion Thaumaturge's Moccasins <-> Scion Sorceress's High Boots
|
||||
AddItem(m2f, f2m, 13327, 15942); // Scion Chronocler's Cowl <-> Scion Healer's Robe
|
||||
AddItem(m2f, f2m, 13700, 10034, true, false); // Scion Chronocler's Ringbands -> The Emperor's New Gloves
|
||||
AddItem(m2f, f2m, 13701, 15943); // Scion Chronocler's Tights <-> Scion Healer's Halftights
|
||||
AddItem(m2f, f2m, 13702, 15944); // Scion Chronocler's Caligae <-> Scion Healer's Highboots
|
||||
AddItem(m2f, f2m, 14861, 13324); // Head Engineer's Goggles <-> Scion Striker's Visor
|
||||
AddItem(m2f, f2m, 14862, 13325); // Head Engineer's Attire <-> Scion Striker's Attire
|
||||
AddItem(m2f, f2m, 15938, 33751); // Scion Rogue's Jacket <-> Oracle Top
|
||||
AddItem(m2f, f2m, 15939, 10034, true, false); // Scion Rogue's Armguards -> The Emperor's New Gloves
|
||||
AddItem(m2f, f2m, 15940, 33752); // Scion Rogue's Gaskins <-> Oracle Leggings
|
||||
AddItem(m2f, f2m, 15941, 33753); // Scion Rogue's Boots <-> Oracle Pantalettes
|
||||
AddItem(m2f, f2m, 16042, 16046); // Abes Jacket <-> High Summoner's Dress
|
||||
AddItem(m2f, f2m, 16043, 16047); // Abes Gloves <-> High Summoner's Armlets
|
||||
AddItem(m2f, f2m, 16044, 10035, true, false); // Abes Halfslops -> The Emperor's New Breeches
|
||||
AddItem(m2f, f2m, 16045, 16048); // Abes Boots <-> High Summoner's Boots
|
||||
AddItem(m2f, f2m, 17473, 28553); // Lord Commander's Coat <-> Majestic Dress
|
||||
AddItem(m2f, f2m, 17474, 28554); // Lord Commander's Gloves <-> Majestic Wristdresses
|
||||
AddItem(m2f, f2m, 10036, 28555, false); // Emperor's New Boots <- Majestic Boots
|
||||
AddItem(m2f, f2m, 21021, 21026); // Werewolf Feet <-> Werewolf Legs
|
||||
AddItem(m2f, f2m, 22452, 20633); // Cracked Manderville Monocle <-> Blackbosom Hat
|
||||
AddItem(m2f, f2m, 22453, 20634); // Torn Manderville Coatee <-> Blackbosom Dress
|
||||
AddItem(m2f, f2m, 22454, 20635); // Singed Manderville Gloves <-> Blackbosom Dress Gloves
|
||||
AddItem(m2f, f2m, 22455, 10035, true, false); // Stained Manderville Bottoms -> The Emperor's New Breeches
|
||||
AddItem(m2f, f2m, 22456, 20636); // Scuffed Manderville Gaiters <-> lackbosom Boots
|
||||
AddItem(m2f, f2m, 23013, 21302); // Doman Liege's Dogi <-> Scion Liberator's Jacket
|
||||
AddItem(m2f, f2m, 23014, 21303); // Doman Liege's Kote <-> Scion Liberator's Fingerless Gloves
|
||||
AddItem(m2f, f2m, 23015, 21304); // Doman Liege's Kyakui <-> Scion Liberator's Pantalettes
|
||||
AddItem(m2f, f2m, 23016, 21305); // Doman Liege's Kyahan <-> Scion Liberator's Sabatons
|
||||
AddItem(m2f, f2m, 09293, 21306, false); // The Emperor's New Earrings <- Scion Liberator's Earrings
|
||||
AddItem(m2f, f2m, 24158, 23008, true, false); // Leal Samurai's Kasa -> Eastern Socialite's Hat
|
||||
AddItem(m2f, f2m, 24159, 23009, true, false); // Leal Samurai's Dogi -> Eastern Socialite's Cheongsam
|
||||
AddItem(m2f, f2m, 24160, 23010, true, false); // Leal Samurai's Tekko -> Eastern Socialite's Gloves
|
||||
AddItem(m2f, f2m, 24161, 23011, true, false); // Leal Samurai's Tsutsu-hakama -> Eastern Socialite's Skirt
|
||||
AddItem(m2f, f2m, 24162, 23012, true, false); // Leal Samurai's Geta -> Eastern Socialite's Boots
|
||||
AddItem(m2f, f2m, 02966, 13321, false); // Reindeer Suit <- Antecedent's Attire
|
||||
AddItem(m2f, f2m, 15479, 36843, false); // Swine Body <- Lyse's Leadership Attire
|
||||
AddItem(m2f, f2m, 21941, 24999, false); // Ala Mhigan Gown <- Gown of Light
|
||||
AddItem(m2f, f2m, 30757, 25000, false); // Southern Seas Skirt <- Skirt of Light
|
||||
AddItem(m2f, f2m, 36821, 27933, false); // Archfiend Helm <- Scion Hearer's Hood
|
||||
AddItem(m2f, f2m, 36822, 27934, false); // Archfiend Armor <- Scion Hearer's Coat
|
||||
AddItem(m2f, f2m, 36825, 27935, false); // Archfiend Sabatons <- Scion Hearer's Shoes
|
||||
AddItem(m2f, f2m, 38253, 38257); // Valentione Emissary's Hat <-> Valentione Emissary's Dress Hat
|
||||
AddItem(m2f, f2m, 38254, 38258); // Valentione Emissary's Jacket <-> Valentione Emissary's Ruffled Dress
|
||||
AddItem(m2f, f2m, 38255, 38259); // Valentione Emissary's Bottoms <-> Valentione Emissary's Culottes
|
||||
AddItem(m2f, f2m, 38256, 38260); // Valentione Emissary's Boots <-> Valentione Emissary's Boots
|
||||
}
|
||||
|
||||
// The racial starter sets are available for all 4 slots each,
|
||||
// but have no associated accessories or hats.
|
||||
private static readonly uint[] RaceGenderGroup =
|
||||
{
|
||||
0x020054,
|
||||
0x020055,
|
||||
0x020056,
|
||||
0x020057,
|
||||
0x02005C,
|
||||
0x02005D,
|
||||
0x020058,
|
||||
0x020059,
|
||||
0x02005A,
|
||||
0x02005B,
|
||||
0x020101,
|
||||
0x020102,
|
||||
0x010255,
|
||||
uint.MaxValue, // TODO: Female Hrothgar
|
||||
0x0102E8,
|
||||
0x010245,
|
||||
};
|
||||
// @Formatter:on
|
||||
}
|
||||
74
Penumbra.GameData/Data/WeaponIdentificationList.cs
Normal file
74
Penumbra.GameData/Data/WeaponIdentificationList.cs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
internal sealed class WeaponIdentificationList : KeyList<Item>
|
||||
{
|
||||
private const string Tag = "WeaponIdentification";
|
||||
private const int Version = 1;
|
||||
|
||||
public WeaponIdentificationList(DalamudPluginInterface pi, ClientLanguage language, DataManager gameData)
|
||||
: base(pi, Tag, language, Version, CreateWeaponList(gameData, language))
|
||||
{ }
|
||||
|
||||
public IEnumerable<Item> Between(SetId modelId)
|
||||
=> Between(ToKey(modelId, 0, 0), ToKey(modelId, 0xFFFF, 0xFF));
|
||||
|
||||
public IEnumerable<Item> Between(SetId modelId, WeaponType type, byte variant = 0)
|
||||
{
|
||||
if (type == 0)
|
||||
return Between(ToKey(modelId, 0, 0), ToKey(modelId, 0xFFFF, 0xFF));
|
||||
if (variant == 0)
|
||||
return Between(ToKey(modelId, type, 0), ToKey(modelId, type, 0xFF));
|
||||
|
||||
return Between(ToKey(modelId, type, variant), ToKey(modelId, type, variant));
|
||||
}
|
||||
|
||||
public void Dispose(DalamudPluginInterface pi, ClientLanguage language)
|
||||
=> DataSharer.DisposeTag(pi, Tag, language, Version);
|
||||
|
||||
public static ulong ToKey(SetId modelId, WeaponType type, byte variant)
|
||||
=> ((ulong)modelId << 32) | ((ulong)type << 16) | variant;
|
||||
|
||||
public static ulong ToKey(Item i, bool offhand)
|
||||
{
|
||||
var quad = offhand ? (Lumina.Data.Parsing.Quad)i.ModelSub : (Lumina.Data.Parsing.Quad)i.ModelMain;
|
||||
return ToKey(quad.A, quad.B, (byte)quad.C);
|
||||
}
|
||||
|
||||
protected override IEnumerable<ulong> ToKeys(Item i)
|
||||
{
|
||||
var key1 = 0ul;
|
||||
if (i.ModelMain != 0)
|
||||
{
|
||||
key1 = ToKey(i, false);
|
||||
yield return key1;
|
||||
}
|
||||
|
||||
if (i.ModelSub != 0)
|
||||
{
|
||||
var key2 = ToKey(i, true);
|
||||
if (key1 != key2)
|
||||
yield return key2;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ValidKey(ulong key)
|
||||
=> key != 0;
|
||||
|
||||
protected override int ValueKeySelector(Item data)
|
||||
=> (int)data.RowId;
|
||||
|
||||
private static IEnumerable<Item> CreateWeaponList(DataManager gameData, ClientLanguage language)
|
||||
{
|
||||
var items = gameData.GetExcelSheet<Item>(language)!;
|
||||
return items.Where(i => (EquipSlot)i.EquipSlotCategory.Row is EquipSlot.MainHand or EquipSlot.OffHand or EquipSlot.BothHand);
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ public enum EquipSlot : byte
|
|||
|
||||
public static class EquipSlotExtensions
|
||||
{
|
||||
public static EquipSlot ToEquipSlot( this uint value )
|
||||
public static EquipSlot ToEquipSlot(this uint value)
|
||||
=> value switch
|
||||
{
|
||||
0 => EquipSlot.Head,
|
||||
|
|
@ -54,7 +54,7 @@ public static class EquipSlotExtensions
|
|||
_ => EquipSlot.Unknown,
|
||||
};
|
||||
|
||||
public static uint ToIndex( this EquipSlot slot )
|
||||
public static uint ToIndex(this EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.Head => 0,
|
||||
|
|
@ -72,7 +72,7 @@ public static class EquipSlotExtensions
|
|||
_ => uint.MaxValue,
|
||||
};
|
||||
|
||||
public static string ToSuffix( this EquipSlot value )
|
||||
public static string ToSuffix(this EquipSlot value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
|
|
@ -90,7 +90,7 @@ public static class EquipSlotExtensions
|
|||
};
|
||||
}
|
||||
|
||||
public static EquipSlot ToSlot( this EquipSlot value )
|
||||
public static EquipSlot ToSlot(this EquipSlot value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
|
|
@ -116,11 +116,11 @@ public static class EquipSlotExtensions
|
|||
EquipSlot.BodyHands => EquipSlot.Body,
|
||||
EquipSlot.BodyLegsFeet => EquipSlot.Body,
|
||||
EquipSlot.ChestHands => EquipSlot.Body,
|
||||
_ => throw new InvalidEnumArgumentException( $"{value} ({( int )value}) is not valid." ),
|
||||
_ => throw new InvalidEnumArgumentException($"{value} ({(int)value}) is not valid."),
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToName( this EquipSlot value )
|
||||
public static string ToName(this EquipSlot value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
|
|
@ -150,7 +150,7 @@ public static class EquipSlotExtensions
|
|||
};
|
||||
}
|
||||
|
||||
public static bool IsEquipment( this EquipSlot value )
|
||||
public static bool IsEquipment(this EquipSlot value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
|
|
@ -163,7 +163,7 @@ public static class EquipSlotExtensions
|
|||
};
|
||||
}
|
||||
|
||||
public static bool IsAccessory( this EquipSlot value )
|
||||
public static bool IsAccessory(this EquipSlot value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
|
|
@ -176,14 +176,40 @@ public static class EquipSlotExtensions
|
|||
};
|
||||
}
|
||||
|
||||
public static readonly EquipSlot[] EquipmentSlots = Enum.GetValues< EquipSlot >().Where( e => e.IsEquipment() ).ToArray();
|
||||
public static readonly EquipSlot[] AccessorySlots = Enum.GetValues< EquipSlot >().Where( e => e.IsAccessory() ).ToArray();
|
||||
public static readonly EquipSlot[] EqdpSlots = EquipmentSlots.Concat( AccessorySlots ).ToArray();
|
||||
public static bool IsEquipmentPiece(this EquipSlot value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
// Accessories
|
||||
EquipSlot.RFinger => true,
|
||||
EquipSlot.Wrists => true,
|
||||
EquipSlot.Ears => true,
|
||||
EquipSlot.Neck => true,
|
||||
// Equipment
|
||||
EquipSlot.Head => true,
|
||||
EquipSlot.Body => true,
|
||||
EquipSlot.Hands => true,
|
||||
EquipSlot.Legs => true,
|
||||
EquipSlot.Feet => true,
|
||||
EquipSlot.BodyHands => true,
|
||||
EquipSlot.BodyHandsLegsFeet => true,
|
||||
EquipSlot.BodyLegsFeet => true,
|
||||
EquipSlot.FullBody => true,
|
||||
EquipSlot.HeadBody => true,
|
||||
EquipSlot.LegsFeet => true,
|
||||
EquipSlot.ChestHands => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public static readonly EquipSlot[] EquipmentSlots = Enum.GetValues<EquipSlot>().Where(e => e.IsEquipment()).ToArray();
|
||||
public static readonly EquipSlot[] AccessorySlots = Enum.GetValues<EquipSlot>().Where(e => e.IsAccessory()).ToArray();
|
||||
public static readonly EquipSlot[] EqdpSlots = EquipmentSlots.Concat(AccessorySlots).ToArray();
|
||||
}
|
||||
|
||||
public static partial class Names
|
||||
{
|
||||
public static readonly Dictionary< string, EquipSlot > SuffixToEquipSlot = new()
|
||||
public static readonly Dictionary<string, EquipSlot> SuffixToEquipSlot = new()
|
||||
{
|
||||
{ EquipSlot.Head.ToSuffix(), EquipSlot.Head },
|
||||
{ EquipSlot.Hands.ToSuffix(), EquipSlot.Hands },
|
||||
|
|
@ -196,4 +222,4 @@ public static partial class Names
|
|||
{ EquipSlot.LFinger.ToSuffix(), EquipSlot.LFinger },
|
||||
{ EquipSlot.Wrists.ToSuffix(), EquipSlot.Wrists },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
Penumbra.GameData/Enums/ModelTypeExtensions.cs
Normal file
26
Penumbra.GameData/Enums/ModelTypeExtensions.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
|
||||
namespace Penumbra.GameData.Enums;
|
||||
|
||||
public static class ModelTypeExtensions
|
||||
{
|
||||
public static string ToName(this CharacterBase.ModelType type)
|
||||
=> type switch
|
||||
{
|
||||
CharacterBase.ModelType.DemiHuman => "Demihuman",
|
||||
CharacterBase.ModelType.Monster => "Monster",
|
||||
CharacterBase.ModelType.Human => "Human",
|
||||
CharacterBase.ModelType.Weapon => "Weapon",
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
public static CharacterBase.ModelType ToModelType(this ObjectType type)
|
||||
=> type switch
|
||||
{
|
||||
ObjectType.DemiHuman => CharacterBase.ModelType.DemiHuman,
|
||||
ObjectType.Monster => CharacterBase.ModelType.Monster,
|
||||
ObjectType.Character => CharacterBase.ModelType.Human,
|
||||
ObjectType.Weapon => CharacterBase.ModelType.Weapon,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
|
@ -58,10 +58,10 @@ public interface IObjectIdentifier : IDisposable
|
|||
/// <param name="weaponType">The secondary model ID for weapons, WeaponType.Zero for equipment and accessories.</param>
|
||||
/// <param name="variant">The variant ID of the model.</param>
|
||||
/// <param name="slot">The equipment slot the piece of equipment uses.</param>
|
||||
public IReadOnlyList<Item> Identify(SetId setId, WeaponType weaponType, ushort variant, EquipSlot slot);
|
||||
public IEnumerable<Item> Identify(SetId setId, WeaponType weaponType, ushort variant, EquipSlot slot);
|
||||
|
||||
/// <inheritdoc cref="Identify(SetId, WeaponType, ushort, EquipSlot)"/>
|
||||
public IReadOnlyList<Item> Identify(SetId setId, ushort variant, EquipSlot slot)
|
||||
public IEnumerable<Item> Identify(SetId setId, ushort variant, EquipSlot slot)
|
||||
=> Identify(setId, 0, variant, slot);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue