mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Remove local GameData
This commit is contained in:
parent
8d7c779439
commit
b8c9a98ba2
69 changed files with 0 additions and 30444 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,225 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Penumbra.GameData.Actors;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct ActorIdentifier : IEquatable<ActorIdentifier>
|
||||
{
|
||||
public static ActorManager? Manager;
|
||||
|
||||
public static readonly ActorIdentifier Invalid = new(IdentifierType.Invalid, 0, 0, 0, ByteString.Empty);
|
||||
|
||||
public enum RetainerType : ushort
|
||||
{
|
||||
Both = 0,
|
||||
Bell = 1,
|
||||
Mannequin = 2,
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
[FieldOffset( 0 )] public readonly IdentifierType Type; // All
|
||||
[FieldOffset( 1 )] public readonly ObjectKind Kind; // Npc, Owned
|
||||
[FieldOffset( 2 )] public readonly ushort HomeWorld; // Player, Owned
|
||||
[FieldOffset( 2 )] public readonly ushort Index; // NPC
|
||||
[FieldOffset( 2 )] public readonly RetainerType Retainer; // Retainer
|
||||
[FieldOffset( 2 )] public readonly ScreenActor Special; // Special
|
||||
[FieldOffset( 4 )] public readonly uint DataId; // Owned, NPC
|
||||
[FieldOffset( 8 )] public readonly ByteString PlayerName; // Player, Owned
|
||||
// @formatter:on
|
||||
|
||||
public ActorIdentifier CreatePermanent()
|
||||
=> new(Type, Kind, Index, DataId, PlayerName.IsEmpty || PlayerName.IsOwned ? PlayerName : PlayerName.Clone());
|
||||
|
||||
public bool Equals(ActorIdentifier other)
|
||||
{
|
||||
if (Type != other.Type)
|
||||
return false;
|
||||
|
||||
return Type switch
|
||||
{
|
||||
IdentifierType.Player => HomeWorld == other.HomeWorld && PlayerName.EqualsCi(other.PlayerName),
|
||||
IdentifierType.Retainer => PlayerName.EqualsCi(other.PlayerName),
|
||||
IdentifierType.Owned => HomeWorld == other.HomeWorld && PlayerName.EqualsCi(other.PlayerName) && Manager.DataIdEquals(this, other),
|
||||
IdentifierType.Special => Special == other.Special,
|
||||
IdentifierType.Npc => Manager.DataIdEquals(this, other)
|
||||
&& (Index == other.Index || Index == ushort.MaxValue || other.Index == ushort.MaxValue),
|
||||
IdentifierType.UnkObject => PlayerName.EqualsCi(other.PlayerName) && Index == other.Index,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is ActorIdentifier other && Equals(other);
|
||||
|
||||
public static bool operator ==(ActorIdentifier lhs, ActorIdentifier rhs)
|
||||
=> lhs.Equals(rhs);
|
||||
|
||||
public static bool operator !=(ActorIdentifier lhs, ActorIdentifier rhs)
|
||||
=> !lhs.Equals(rhs);
|
||||
|
||||
public bool IsValid
|
||||
=> Type is not (IdentifierType.UnkObject or IdentifierType.Invalid);
|
||||
|
||||
public string Incognito(string? name)
|
||||
{
|
||||
name ??= ToString();
|
||||
if (Type is not (IdentifierType.Player or IdentifierType.Owned))
|
||||
return name;
|
||||
|
||||
var parts = name.Split(' ', 3);
|
||||
return string.Join(" ",
|
||||
parts.Length != 3 ? parts.Select(n => $"{n[0]}.") : parts[..2].Select(n => $"{n[0]}.").Append(parts[2]));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Manager?.ToString(this)
|
||||
?? Type switch
|
||||
{
|
||||
IdentifierType.Player => $"{PlayerName} ({HomeWorld})",
|
||||
IdentifierType.Retainer => $"{PlayerName} (Retainer)",
|
||||
IdentifierType.Owned => $"{PlayerName}s {Kind.ToName()} {DataId} ({HomeWorld})",
|
||||
IdentifierType.Special => Special.ToName(),
|
||||
IdentifierType.Npc =>
|
||||
Index == ushort.MaxValue
|
||||
? $"{Kind.ToName()} #{DataId}"
|
||||
: $"{Kind.ToName()} #{DataId} at {Index}",
|
||||
IdentifierType.UnkObject => PlayerName.IsEmpty
|
||||
? $"Unknown Object at {Index}"
|
||||
: $"{PlayerName} at {Index}",
|
||||
_ => "Invalid",
|
||||
};
|
||||
|
||||
public string ToName()
|
||||
=> Manager?.ToName(this) ?? "Unknown Object";
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Type switch
|
||||
{
|
||||
IdentifierType.Player => HashCode.Combine(IdentifierType.Player, PlayerName, HomeWorld),
|
||||
IdentifierType.Retainer => HashCode.Combine(IdentifierType.Player, PlayerName),
|
||||
IdentifierType.Owned => HashCode.Combine(IdentifierType.Owned, Kind, PlayerName, HomeWorld, DataId),
|
||||
IdentifierType.Special => HashCode.Combine(IdentifierType.Special, Special),
|
||||
IdentifierType.Npc => HashCode.Combine(IdentifierType.Npc, Kind, DataId),
|
||||
IdentifierType.UnkObject => HashCode.Combine(IdentifierType.UnkObject, PlayerName, Index),
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
internal ActorIdentifier(IdentifierType type, ObjectKind kind, ushort index, uint data, ByteString playerName)
|
||||
{
|
||||
Type = type;
|
||||
Kind = kind;
|
||||
Special = (ScreenActor)index;
|
||||
HomeWorld = Index = index;
|
||||
DataId = data;
|
||||
PlayerName = playerName;
|
||||
}
|
||||
|
||||
public JObject ToJson()
|
||||
{
|
||||
var ret = new JObject { { nameof(Type), Type.ToString() } };
|
||||
switch (Type)
|
||||
{
|
||||
case IdentifierType.Player:
|
||||
ret.Add(nameof(PlayerName), PlayerName.ToString());
|
||||
ret.Add(nameof(HomeWorld), HomeWorld);
|
||||
return ret;
|
||||
case IdentifierType.Retainer:
|
||||
ret.Add(nameof(PlayerName), PlayerName.ToString());
|
||||
return ret;
|
||||
case IdentifierType.Owned:
|
||||
ret.Add(nameof(PlayerName), PlayerName.ToString());
|
||||
ret.Add(nameof(HomeWorld), HomeWorld);
|
||||
ret.Add(nameof(Kind), Kind.ToString());
|
||||
ret.Add(nameof(DataId), DataId);
|
||||
return ret;
|
||||
case IdentifierType.Special:
|
||||
ret.Add(nameof(Special), Special.ToString());
|
||||
return ret;
|
||||
case IdentifierType.Npc:
|
||||
ret.Add(nameof(Kind), Kind.ToString());
|
||||
if (Index != ushort.MaxValue)
|
||||
ret.Add(nameof(Index), Index);
|
||||
ret.Add(nameof(DataId), DataId);
|
||||
return ret;
|
||||
case IdentifierType.UnkObject:
|
||||
ret.Add(nameof(PlayerName), PlayerName.ToString());
|
||||
ret.Add(nameof(Index), Index);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ActorManagerExtensions
|
||||
{
|
||||
public static bool DataIdEquals(this ActorManager? manager, ActorIdentifier lhs, ActorIdentifier rhs)
|
||||
{
|
||||
if (lhs.Kind != rhs.Kind)
|
||||
return false;
|
||||
|
||||
if (lhs.DataId == rhs.DataId)
|
||||
return true;
|
||||
|
||||
if (manager == null)
|
||||
return lhs.Kind == rhs.Kind && lhs.DataId == rhs.DataId || lhs.DataId == uint.MaxValue || rhs.DataId == uint.MaxValue;
|
||||
|
||||
var dict = lhs.Kind switch
|
||||
{
|
||||
ObjectKind.MountType => manager.Data.Mounts,
|
||||
ObjectKind.Companion => manager.Data.Companions,
|
||||
ObjectKind.Ornament => manager.Data.Ornaments,
|
||||
ObjectKind.BattleNpc => manager.Data.BNpcs,
|
||||
ObjectKind.EventNpc => manager.Data.ENpcs,
|
||||
_ => new Dictionary<uint, string>(),
|
||||
};
|
||||
|
||||
return dict.TryGetValue(lhs.DataId, out var lhsName)
|
||||
&& dict.TryGetValue(rhs.DataId, out var rhsName)
|
||||
&& lhsName.Equals(rhsName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static string ToName(this ObjectKind kind)
|
||||
=> kind switch
|
||||
{
|
||||
ObjectKind.None => "Unknown",
|
||||
ObjectKind.BattleNpc => "Battle NPC",
|
||||
ObjectKind.EventNpc => "Event NPC",
|
||||
ObjectKind.MountType => "Mount",
|
||||
ObjectKind.Companion => "Companion",
|
||||
ObjectKind.Ornament => "Accessory",
|
||||
_ => kind.ToString(),
|
||||
};
|
||||
|
||||
public static string ToName(this IdentifierType type)
|
||||
=> type switch
|
||||
{
|
||||
IdentifierType.Player => "Player",
|
||||
IdentifierType.Retainer => "Retainer (Bell)",
|
||||
IdentifierType.Owned => "Owned NPC",
|
||||
IdentifierType.Special => "Special Actor",
|
||||
IdentifierType.Npc => "NPC",
|
||||
IdentifierType.UnkObject => "Unknown Object",
|
||||
_ => "Invalid",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Fixed names for special actors.
|
||||
/// </summary>
|
||||
public static string ToName(this ScreenActor actor)
|
||||
=> actor switch
|
||||
{
|
||||
ScreenActor.CharacterScreen => "Character Screen Actor",
|
||||
ScreenActor.ExamineScreen => "Examine Screen Actor",
|
||||
ScreenActor.FittingRoom => "Fitting Room Actor",
|
||||
ScreenActor.DyePreview => "Dye Preview Actor",
|
||||
ScreenActor.Portrait => "Portrait Actor",
|
||||
_ => "Invalid",
|
||||
};
|
||||
}
|
||||
|
|
@ -1,406 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Text;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String;
|
||||
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
|
||||
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||
|
||||
namespace Penumbra.GameData.Actors;
|
||||
|
||||
public sealed partial class ActorManager : IDisposable
|
||||
{
|
||||
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 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 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; }
|
||||
|
||||
public ActorManagerData(DalamudPluginInterface pluginInterface, DataManager gameData, ClientLanguage language)
|
||||
: base(pluginInterface, language, 2)
|
||||
{
|
||||
var worldTask = TryCatchDataAsync("Worlds", CreateWorldData(gameData));
|
||||
var mountsTask = TryCatchDataAsync("Mounts", CreateMountData(gameData));
|
||||
var companionsTask = TryCatchDataAsync("Companions", CreateCompanionData(gameData));
|
||||
var ornamentsTask = TryCatchDataAsync("Ornaments", CreateOrnamentData(gameData));
|
||||
var bNpcsTask = TryCatchDataAsync("BNpcs", CreateBNpcData(gameData));
|
||||
var eNpcsTask = TryCatchDataAsync("ENpcs", CreateENpcData(gameData));
|
||||
|
||||
Worlds = worldTask.Result;
|
||||
Mounts = mountsTask.Result;
|
||||
Companions = companionsTask.Result;
|
||||
Ornaments = ornamentsTask.Result;
|
||||
BNpcs = bNpcsTask.Result;
|
||||
ENpcs = eNpcsTask.Result;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Return the world id corresponding to the given name.
|
||||
/// </summary>
|
||||
/// <returns>ushort.MaxValue if the name is empty, 0 if it is not a valid world, or the worlds id.</returns>
|
||||
public ushort ToWorldId(string worldName)
|
||||
=> worldName.Length != 0
|
||||
? Worlds.FirstOrDefault(kvp => string.Equals(kvp.Value, worldName, StringComparison.OrdinalIgnoreCase), default).Key
|
||||
: ushort.MaxValue;
|
||||
|
||||
/// <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.Ornament => Ornaments.TryGetValue(dataId, out name),
|
||||
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 Action<Dictionary<ushort, string>> CreateWorldData(DataManager gameData)
|
||||
=> d =>
|
||||
{
|
||||
foreach (var w in gameData.GetExcelSheet<World>(Language)!.Where(w => w.IsPublic && !w.Name.RawData.IsEmpty))
|
||||
d.TryAdd((ushort)w.RowId, string.Intern(w.Name.ToDalamudString().TextValue));
|
||||
};
|
||||
|
||||
private Action<Dictionary<uint, string>> CreateMountData(DataManager gameData)
|
||||
=> d =>
|
||||
{
|
||||
foreach (var m in gameData.GetExcelSheet<Mount>(Language)!.Where(m => m.Singular.RawData.Length > 0 && m.Order >= 0))
|
||||
d.TryAdd(m.RowId, ToTitleCaseExtended(m.Singular, m.Article));
|
||||
};
|
||||
|
||||
private Action<Dictionary<uint, string>> CreateCompanionData(DataManager gameData)
|
||||
=> d =>
|
||||
{
|
||||
foreach (var c in gameData.GetExcelSheet<Companion>(Language)!.Where(c
|
||||
=> c.Singular.RawData.Length > 0 && c.Order < ushort.MaxValue))
|
||||
d.TryAdd(c.RowId, ToTitleCaseExtended(c.Singular, c.Article));
|
||||
};
|
||||
|
||||
private Action<Dictionary<uint, string>> CreateOrnamentData(DataManager gameData)
|
||||
=> d =>
|
||||
{
|
||||
foreach (var o in gameData.GetExcelSheet<Ornament>(Language)!.Where(o => o.Singular.RawData.Length > 0))
|
||||
d.TryAdd(o.RowId, ToTitleCaseExtended(o.Singular, o.Article));
|
||||
};
|
||||
|
||||
private Action<Dictionary<uint, string>> CreateBNpcData(DataManager gameData)
|
||||
=> d =>
|
||||
{
|
||||
foreach (var n in gameData.GetExcelSheet<BNpcName>(Language)!.Where(n => n.Singular.RawData.Length > 0))
|
||||
d.TryAdd(n.RowId, ToTitleCaseExtended(n.Singular, n.Article));
|
||||
};
|
||||
|
||||
private Action<Dictionary<uint, string>> CreateENpcData(DataManager gameData)
|
||||
=> d =>
|
||||
{
|
||||
foreach (var n in gameData.GetExcelSheet<ENpcResident>(Language)!.Where(e => e.Singular.RawData.Length > 0))
|
||||
d.TryAdd(n.RowId, ToTitleCaseExtended(n.Singular, n.Article));
|
||||
};
|
||||
|
||||
private static string ToTitleCaseExtended(SeString s, sbyte article)
|
||||
{
|
||||
if (article == 1)
|
||||
return string.Intern(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 string.Intern(sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public readonly ActorManagerData Data;
|
||||
|
||||
public ActorManager(DalamudPluginInterface pluginInterface, ObjectTable objects, ClientState state, Dalamud.Game.Framework framework,
|
||||
DataManager gameData, GameGui gameGui,
|
||||
Func<ushort, short> toParentIdx)
|
||||
: this(pluginInterface, objects, state, framework, gameData, gameGui, gameData.Language, toParentIdx)
|
||||
{ }
|
||||
|
||||
public ActorManager(DalamudPluginInterface pluginInterface, ObjectTable objects, ClientState state, Dalamud.Game.Framework framework,
|
||||
DataManager gameData, GameGui gameGui,
|
||||
ClientLanguage language, Func<ushort, short> toParentIdx)
|
||||
{
|
||||
_framework = framework;
|
||||
_objects = objects;
|
||||
_gameGui = gameGui;
|
||||
_clientState = state;
|
||||
_toParentIdx = toParentIdx;
|
||||
Data = new ActorManagerData(pluginInterface, gameData, language);
|
||||
|
||||
ActorIdentifier.Manager = this;
|
||||
|
||||
SignatureHelper.Initialise(this);
|
||||
}
|
||||
|
||||
public unsafe ActorIdentifier GetCurrentPlayer()
|
||||
{
|
||||
var address = (Character*)_objects.GetObjectAddress(0);
|
||||
return address == null
|
||||
? ActorIdentifier.Invalid
|
||||
: CreateIndividualUnchecked(IdentifierType.Player, new ByteString(address->GameObject.Name), address->HomeWorld,
|
||||
ObjectKind.None, uint.MaxValue);
|
||||
}
|
||||
|
||||
public ActorIdentifier GetInspectPlayer()
|
||||
{
|
||||
var addon = _gameGui.GetAddonByName("CharacterInspect", 1);
|
||||
if (addon == IntPtr.Zero)
|
||||
return ActorIdentifier.Invalid;
|
||||
|
||||
return CreatePlayer(InspectName, InspectWorldId);
|
||||
}
|
||||
|
||||
public unsafe bool ResolvePartyBannerPlayer(ScreenActor type, out ActorIdentifier id)
|
||||
{
|
||||
id = ActorIdentifier.Invalid;
|
||||
var module = Framework.Instance()->GetUiModule()->GetAgentModule();
|
||||
if (module == null)
|
||||
return false;
|
||||
|
||||
var agent = (AgentBannerInterface*)module->GetAgentByInternalId(AgentId.BannerParty);
|
||||
if (agent == null || !agent->AgentInterface.IsAgentActive())
|
||||
agent = (AgentBannerInterface*)module->GetAgentByInternalId(AgentId.BannerMIP);
|
||||
if (agent == null || !agent->AgentInterface.IsAgentActive())
|
||||
return false;
|
||||
|
||||
var idx = (ushort)type - (ushort)ScreenActor.CharacterScreen;
|
||||
var character = agent->Character(idx);
|
||||
if (character == null)
|
||||
return true;
|
||||
|
||||
var name = new ByteString(character->Name1.StringPtr);
|
||||
id = CreatePlayer(name, (ushort)character->WorldId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe bool SearchPlayerCustomize(Character* character, int idx, out ActorIdentifier id)
|
||||
{
|
||||
var other = (Character*)_objects.GetObjectAddress(idx);
|
||||
if (other == null || !CustomizeData.ScreenActorEquals((CustomizeData*)character->DrawData.CustomizeData.Data, (CustomizeData*)other->DrawData.CustomizeData.Data))
|
||||
{
|
||||
id = ActorIdentifier.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
id = FromObject(&other->GameObject, out _, false, true, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe ActorIdentifier SearchPlayersCustomize(Character* gameObject, int idx1, int idx2, int idx3)
|
||||
=> SearchPlayerCustomize(gameObject, idx1, out var ret)
|
||||
|| SearchPlayerCustomize(gameObject, idx2, out ret)
|
||||
|| SearchPlayerCustomize(gameObject, idx3, out ret)
|
||||
? ret
|
||||
: ActorIdentifier.Invalid;
|
||||
|
||||
private unsafe ActorIdentifier SearchPlayersCustomize(Character* gameObject)
|
||||
{
|
||||
static bool Compare(Character* a, Character* b)
|
||||
{
|
||||
var data1 = (CustomizeData*)a->DrawData.CustomizeData.Data;
|
||||
var data2 = (CustomizeData*)b->DrawData.CustomizeData.Data;
|
||||
var equals = CustomizeData.ScreenActorEquals(data1, data2);
|
||||
return equals;
|
||||
}
|
||||
|
||||
for (var i = 0; i < (int)ScreenActor.CutsceneStart; i += 2)
|
||||
{
|
||||
var obj = (GameObject*)_objects.GetObjectAddress(i);
|
||||
if (obj != null
|
||||
&& obj->ObjectKind is (byte)ObjectKind.Player
|
||||
&& Compare(gameObject, (Character*)obj))
|
||||
return FromObject(obj, out _, false, true, false);
|
||||
}
|
||||
|
||||
return ActorIdentifier.Invalid;
|
||||
}
|
||||
|
||||
public unsafe bool ResolveMahjongPlayer(ScreenActor type, out ActorIdentifier id)
|
||||
{
|
||||
id = ActorIdentifier.Invalid;
|
||||
if (_clientState.TerritoryType != 831 && _gameGui.GetAddonByName("EmjIntro") == IntPtr.Zero)
|
||||
return false;
|
||||
|
||||
var obj = (Character*)_objects.GetObjectAddress((int)type);
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
id = type switch
|
||||
{
|
||||
ScreenActor.CharacterScreen => GetCurrentPlayer(),
|
||||
ScreenActor.ExamineScreen => SearchPlayersCustomize(obj, 2, 4, 6),
|
||||
ScreenActor.FittingRoom => SearchPlayersCustomize(obj, 4, 2, 6),
|
||||
ScreenActor.DyePreview => SearchPlayersCustomize(obj, 6, 2, 4),
|
||||
_ => ActorIdentifier.Invalid,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
public unsafe bool ResolvePvPBannerPlayer(ScreenActor type, out ActorIdentifier id)
|
||||
{
|
||||
id = ActorIdentifier.Invalid;
|
||||
if (!_clientState.IsPvPExcludingDen)
|
||||
return false;
|
||||
|
||||
var addon = (AtkUnitBase*)_gameGui.GetAddonByName("PvPMap");
|
||||
if (addon == null || addon->IsVisible)
|
||||
return false;
|
||||
|
||||
var obj = (Character*)_objects.GetObjectAddress((int)type);
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
id = type switch
|
||||
{
|
||||
ScreenActor.CharacterScreen => SearchPlayersCustomize(obj),
|
||||
ScreenActor.ExamineScreen => SearchPlayersCustomize(obj),
|
||||
ScreenActor.FittingRoom => SearchPlayersCustomize(obj),
|
||||
ScreenActor.DyePreview => SearchPlayersCustomize(obj),
|
||||
ScreenActor.Portrait => SearchPlayersCustomize(obj),
|
||||
_ => ActorIdentifier.Invalid,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
public unsafe ActorIdentifier GetCardPlayer()
|
||||
{
|
||||
var agent = AgentCharaCard.Instance();
|
||||
if (agent == null || agent->Data == null)
|
||||
return ActorIdentifier.Invalid;
|
||||
|
||||
var worldId = *(ushort*)((byte*)agent->Data + Offsets.AgentCharaCardDataWorldId);
|
||||
return CreatePlayer(new ByteString(agent->Data->Name.StringPtr), worldId);
|
||||
}
|
||||
|
||||
public ActorIdentifier GetGlamourPlayer()
|
||||
{
|
||||
var addon = _gameGui.GetAddonByName("MiragePrismMiragePlate", 1);
|
||||
return addon == IntPtr.Zero ? ActorIdentifier.Invalid : GetCurrentPlayer();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Data.Dispose();
|
||||
if (ActorIdentifier.Manager == this)
|
||||
ActorIdentifier.Manager = null;
|
||||
}
|
||||
|
||||
~ActorManager()
|
||||
=> Dispose();
|
||||
|
||||
private readonly Dalamud.Game.Framework _framework;
|
||||
private readonly ObjectTable _objects;
|
||||
private readonly ClientState _clientState;
|
||||
private readonly GameGui _gameGui;
|
||||
|
||||
private readonly Func<ushort, short> _toParentIdx;
|
||||
|
||||
[Signature(Sigs.InspectTitleId, ScanType = ScanType.StaticAddress)]
|
||||
private static unsafe ushort* _inspectTitleId = null!;
|
||||
|
||||
[Signature(Sigs.InspectWorldId, ScanType = ScanType.StaticAddress)]
|
||||
private static unsafe ushort* _inspectWorldId = null!;
|
||||
|
||||
private static unsafe ushort InspectTitleId
|
||||
=> *_inspectTitleId;
|
||||
|
||||
private static unsafe ByteString InspectName
|
||||
=> new((byte*)(_inspectWorldId + 1));
|
||||
|
||||
private static unsafe ushort InspectWorldId
|
||||
=> *_inspectWorldId;
|
||||
|
||||
public static readonly IReadOnlySet<uint> MannequinIds = new HashSet<uint>()
|
||||
{
|
||||
1026228u,
|
||||
1026229u,
|
||||
1026986u,
|
||||
1026987u,
|
||||
1026988u,
|
||||
1026989u,
|
||||
1032291u,
|
||||
1032292u,
|
||||
1032293u,
|
||||
1032294u,
|
||||
1033046u,
|
||||
1033047u,
|
||||
1033658u,
|
||||
1033659u,
|
||||
1007137u,
|
||||
// TODO: Female Hrothgar
|
||||
};
|
||||
}
|
||||
|
|
@ -1,606 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.String;
|
||||
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
|
||||
|
||||
namespace Penumbra.GameData.Actors;
|
||||
|
||||
public partial class ActorManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Try to create an ActorIdentifier from a already parsed JObject <paramref name="data"/>.
|
||||
/// </summary>
|
||||
/// <param name="data">A parsed JObject</param>
|
||||
/// <returns>ActorIdentifier.Invalid if the JObject can not be converted, a valid ActorIdentifier otherwise.</returns>
|
||||
public ActorIdentifier FromJson(JObject? data)
|
||||
{
|
||||
if (data == null)
|
||||
return ActorIdentifier.Invalid;
|
||||
|
||||
var type = data[nameof(ActorIdentifier.Type)]?.ToObject<IdentifierType>() ?? IdentifierType.Invalid;
|
||||
switch (type)
|
||||
{
|
||||
case IdentifierType.Player:
|
||||
{
|
||||
var name = ByteString.FromStringUnsafe(data[nameof(ActorIdentifier.PlayerName)]?.ToObject<string>(), false);
|
||||
var homeWorld = data[nameof(ActorIdentifier.HomeWorld)]?.ToObject<ushort>() ?? 0;
|
||||
return CreatePlayer(name, homeWorld);
|
||||
}
|
||||
case IdentifierType.Retainer:
|
||||
{
|
||||
var name = ByteString.FromStringUnsafe(data[nameof(ActorIdentifier.PlayerName)]?.ToObject<string>(), false);
|
||||
return CreateRetainer(name, 0);
|
||||
}
|
||||
case IdentifierType.Owned:
|
||||
{
|
||||
var name = ByteString.FromStringUnsafe(data[nameof(ActorIdentifier.PlayerName)]?.ToObject<string>(), false);
|
||||
var homeWorld = data[nameof(ActorIdentifier.HomeWorld)]?.ToObject<ushort>() ?? 0;
|
||||
var kind = data[nameof(ActorIdentifier.Kind)]?.ToObject<ObjectKind>() ?? ObjectKind.CardStand;
|
||||
var dataId = data[nameof(ActorIdentifier.DataId)]?.ToObject<uint>() ?? 0;
|
||||
return CreateOwned(name, homeWorld, kind, dataId);
|
||||
}
|
||||
case IdentifierType.Special:
|
||||
{
|
||||
var special = data[nameof(ActorIdentifier.Special)]?.ToObject<ScreenActor>() ?? 0;
|
||||
return CreateSpecial(special);
|
||||
}
|
||||
case IdentifierType.Npc:
|
||||
{
|
||||
var index = data[nameof(ActorIdentifier.Index)]?.ToObject<ushort>() ?? ushort.MaxValue;
|
||||
var kind = data[nameof(ActorIdentifier.Kind)]?.ToObject<ObjectKind>() ?? ObjectKind.CardStand;
|
||||
var dataId = data[nameof(ActorIdentifier.DataId)]?.ToObject<uint>() ?? 0;
|
||||
return CreateNpc(kind, dataId, index);
|
||||
}
|
||||
case IdentifierType.UnkObject:
|
||||
{
|
||||
var index = data[nameof(ActorIdentifier.Index)]?.ToObject<ushort>() ?? ushort.MaxValue;
|
||||
var name = ByteString.FromStringUnsafe(data[nameof(ActorIdentifier.PlayerName)]?.ToObject<string>(), false);
|
||||
return CreateIndividualUnchecked(IdentifierType.UnkObject, name, index, ObjectKind.None, 0);
|
||||
}
|
||||
default: return ActorIdentifier.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use stored data to convert an ActorIdentifier to a string.
|
||||
/// </summary>
|
||||
public string ToString(ActorIdentifier id)
|
||||
{
|
||||
return id.Type switch
|
||||
{
|
||||
IdentifierType.Player => id.HomeWorld != _clientState.LocalPlayer?.HomeWorld.Id
|
||||
? $"{id.PlayerName} ({Data.ToWorldName(id.HomeWorld)})"
|
||||
: id.PlayerName.ToString(),
|
||||
IdentifierType.Retainer => id.PlayerName.ToString(),
|
||||
IdentifierType.Owned => id.HomeWorld != _clientState.LocalPlayer?.HomeWorld.Id
|
||||
? $"{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
|
||||
? 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}",
|
||||
_ => "Invalid",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use stored data to convert an ActorIdentifier to a name only.
|
||||
/// </summary>
|
||||
public string ToName(ActorIdentifier id)
|
||||
{
|
||||
return id.Type switch
|
||||
{
|
||||
IdentifierType.Player => id.PlayerName.ToString(),
|
||||
IdentifierType.Retainer => id.PlayerName.ToString(),
|
||||
IdentifierType.Owned => $"{id.PlayerName}s {Data.ToName(id.Kind, id.DataId)}",
|
||||
IdentifierType.Special => id.Special.ToName(),
|
||||
IdentifierType.Npc => Data.ToName(id.Kind, id.DataId),
|
||||
IdentifierType.UnkObject => id.PlayerName.IsEmpty ? id.PlayerName.ToString() : "Unknown Object",
|
||||
_ => "Invalid",
|
||||
};
|
||||
}
|
||||
|
||||
private unsafe FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* HandleCutscene(
|
||||
FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* main)
|
||||
{
|
||||
if (main == null)
|
||||
return null;
|
||||
|
||||
if (main->ObjectIndex is >= (ushort)ScreenActor.CutsceneStart and < (ushort)ScreenActor.CutsceneEnd)
|
||||
{
|
||||
var parentIdx = _toParentIdx(main->ObjectIndex);
|
||||
if (parentIdx >= 0)
|
||||
return (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_objects.GetObjectAddress(parentIdx);
|
||||
}
|
||||
|
||||
return main;
|
||||
}
|
||||
|
||||
public class IdentifierParseError : Exception
|
||||
{
|
||||
public IdentifierParseError(string reason)
|
||||
: base(reason)
|
||||
{ }
|
||||
}
|
||||
|
||||
public ActorIdentifier FromUserString(string userString)
|
||||
{
|
||||
if (userString.Length == 0)
|
||||
throw new IdentifierParseError("The identifier string was empty.");
|
||||
|
||||
var split = userString.Split('|', 3, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
if (split.Length < 2)
|
||||
throw new IdentifierParseError($"The identifier string {userString} does not contain a type and a value.");
|
||||
|
||||
IdentifierType type;
|
||||
var playerName = ByteString.Empty;
|
||||
ushort worldId = 0;
|
||||
var kind = ObjectKind.Player;
|
||||
var objectId = 0u;
|
||||
|
||||
(ByteString, ushort) ParsePlayer(string player)
|
||||
{
|
||||
var parts = player.Split('@', 2, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
if (!VerifyPlayerName(parts[0]))
|
||||
throw new IdentifierParseError($"{parts[0]} is not a valid player name.");
|
||||
if (!ByteString.FromString(parts[0], out var p))
|
||||
throw new IdentifierParseError($"The player string {parts[0]} contains invalid symbols.");
|
||||
|
||||
var world = parts.Length == 2
|
||||
? Data.ToWorldId(parts[1])
|
||||
: ushort.MaxValue;
|
||||
|
||||
if (!VerifyWorld(world))
|
||||
throw new IdentifierParseError($"{parts[1]} is not a valid world name.");
|
||||
|
||||
return (p, world);
|
||||
}
|
||||
|
||||
(ObjectKind, uint) ParseNpc(string npc)
|
||||
{
|
||||
var split = npc.Split(':', 2, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
if (split.Length != 2)
|
||||
throw new IdentifierParseError("NPCs need to be specified by '[Object Type]:[NPC Name]'.");
|
||||
|
||||
static bool FindDataId(string name, IReadOnlyDictionary<uint, string> data, out uint dataId)
|
||||
{
|
||||
var kvp = data.FirstOrDefault(kvp => kvp.Value.Equals(name, StringComparison.OrdinalIgnoreCase),
|
||||
new KeyValuePair<uint, string>(uint.MaxValue, string.Empty));
|
||||
dataId = kvp.Key;
|
||||
return kvp.Value.Length > 0;
|
||||
}
|
||||
|
||||
switch (split[0].ToLowerInvariant())
|
||||
{
|
||||
case "m":
|
||||
case "mount":
|
||||
return FindDataId(split[1], Data.Mounts, out var id)
|
||||
? (ObjectKind.MountType, id)
|
||||
: throw new IdentifierParseError($"Could not identify a Mount named {split[1]}.");
|
||||
case "c":
|
||||
case "companion":
|
||||
case "minion":
|
||||
case "mini":
|
||||
return FindDataId(split[1], Data.Companions, out id)
|
||||
? (ObjectKind.Companion, id)
|
||||
: throw new IdentifierParseError($"Could not identify a Minion named {split[1]}.");
|
||||
case "a":
|
||||
case "o":
|
||||
case "accessory":
|
||||
case "ornament":
|
||||
return FindDataId(split[1], Data.Ornaments, out id)
|
||||
? (ObjectKind.Ornament, id)
|
||||
: throw new IdentifierParseError($"Could not identify an Accessory named {split[1]}.");
|
||||
case "e":
|
||||
case "enpc":
|
||||
case "eventnpc":
|
||||
case "event npc":
|
||||
return FindDataId(split[1], Data.ENpcs, out id)
|
||||
? (ObjectKind.EventNpc, id)
|
||||
: throw new IdentifierParseError($"Could not identify an Event NPC named {split[1]}.");
|
||||
case "b":
|
||||
case "bnpc":
|
||||
case "battlenpc":
|
||||
case "battle npc":
|
||||
return FindDataId(split[1], Data.BNpcs, out id)
|
||||
? (ObjectKind.BattleNpc, id)
|
||||
: throw new IdentifierParseError($"Could not identify a Battle NPC named {split[1]}.");
|
||||
default: throw new IdentifierParseError($"The argument {split[0]} is not a valid NPC Type.");
|
||||
}
|
||||
}
|
||||
|
||||
switch (split[0].ToLowerInvariant())
|
||||
{
|
||||
case "p":
|
||||
case "player":
|
||||
type = IdentifierType.Player;
|
||||
(playerName, worldId) = ParsePlayer(split[1]);
|
||||
break;
|
||||
case "r":
|
||||
case "retainer":
|
||||
type = IdentifierType.Retainer;
|
||||
if (!VerifyRetainerName(split[1]))
|
||||
throw new IdentifierParseError($"{split[1]} is not a valid player name.");
|
||||
if (!ByteString.FromString(split[1], out playerName))
|
||||
throw new IdentifierParseError($"The retainer string {split[1]} contains invalid symbols.");
|
||||
|
||||
break;
|
||||
case "n":
|
||||
case "npc":
|
||||
type = IdentifierType.Npc;
|
||||
(kind, objectId) = ParseNpc(split[1]);
|
||||
break;
|
||||
case "o":
|
||||
case "owned":
|
||||
if (split.Length < 3)
|
||||
throw new IdentifierParseError(
|
||||
"Owned NPCs need a NPC and a player, separated by '|', but only one was provided.");
|
||||
|
||||
type = IdentifierType.Owned;
|
||||
(kind, objectId) = ParseNpc(split[1]);
|
||||
(playerName, worldId) = ParsePlayer(split[2]);
|
||||
break;
|
||||
default:
|
||||
throw new IdentifierParseError(
|
||||
$"{split[0]} is not a valid identifier type. Valid types are [P]layer, [R]etainer, [N]PC, or [O]wned");
|
||||
}
|
||||
|
||||
return CreateIndividualUnchecked(type, playerName, worldId, kind, objectId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute an ActorIdentifier from a GameObject. If check is true, the values are checked for validity.
|
||||
/// </summary>
|
||||
public unsafe ActorIdentifier FromObject(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* actor,
|
||||
out FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* owner, bool allowPlayerNpc, bool check, bool withoutIndex)
|
||||
{
|
||||
owner = null;
|
||||
if (actor == null)
|
||||
return ActorIdentifier.Invalid;
|
||||
|
||||
actor = HandleCutscene(actor);
|
||||
var idx = actor->ObjectIndex;
|
||||
if (idx is >= (ushort)ScreenActor.CharacterScreen and <= (ushort)ScreenActor.Card8)
|
||||
return CreateIndividualUnchecked(IdentifierType.Special, ByteString.Empty, idx, ObjectKind.None, uint.MaxValue);
|
||||
|
||||
var kind = (ObjectKind)actor->ObjectKind;
|
||||
switch (kind)
|
||||
{
|
||||
case ObjectKind.Player:
|
||||
{
|
||||
var name = new ByteString(actor->Name);
|
||||
var homeWorld = ((Character*)actor)->HomeWorld;
|
||||
return check
|
||||
? CreatePlayer(name, homeWorld)
|
||||
: CreateIndividualUnchecked(IdentifierType.Player, name, homeWorld, ObjectKind.None, uint.MaxValue);
|
||||
}
|
||||
case ObjectKind.BattleNpc:
|
||||
{
|
||||
var ownerId = actor->OwnerID;
|
||||
// 952 -> 780 is a special case for chocobos because they have NameId == 0 otherwise.
|
||||
var nameId = actor->DataID == 952 ? 780 : ((Character*)actor)->NameID;
|
||||
if (ownerId != 0xE0000000)
|
||||
{
|
||||
owner = HandleCutscene(
|
||||
(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)(_objects.SearchById(ownerId)?.Address ?? IntPtr.Zero));
|
||||
if (owner == null)
|
||||
return ActorIdentifier.Invalid;
|
||||
|
||||
var name = new ByteString(owner->Name);
|
||||
var homeWorld = ((Character*)owner)->HomeWorld;
|
||||
return check
|
||||
? CreateOwned(name, homeWorld, ObjectKind.BattleNpc, nameId)
|
||||
: CreateIndividualUnchecked(IdentifierType.Owned, name, homeWorld, ObjectKind.BattleNpc, nameId);
|
||||
}
|
||||
|
||||
// Hack to support Anamnesis changing ObjectKind for NPC faces.
|
||||
if (nameId == 0 && allowPlayerNpc)
|
||||
{
|
||||
var name = new ByteString(actor->Name);
|
||||
if (!name.IsEmpty)
|
||||
{
|
||||
var homeWorld = ((Character*)actor)->HomeWorld;
|
||||
return check
|
||||
? CreatePlayer(name, homeWorld)
|
||||
: CreateIndividualUnchecked(IdentifierType.Player, name, homeWorld, ObjectKind.None, uint.MaxValue);
|
||||
}
|
||||
}
|
||||
|
||||
var index = withoutIndex ? ushort.MaxValue : actor->ObjectIndex;
|
||||
return check
|
||||
? CreateNpc(ObjectKind.BattleNpc, nameId, index)
|
||||
: CreateIndividualUnchecked(IdentifierType.Npc, ByteString.Empty, index, ObjectKind.BattleNpc, nameId);
|
||||
}
|
||||
case ObjectKind.EventNpc:
|
||||
{
|
||||
var dataId = actor->DataID;
|
||||
// Special case for squadron that is also in the game functions, cf. E8 ?? ?? ?? ?? 89 87 ?? ?? ?? ?? 4C 89 BF
|
||||
if (dataId == 0xF845D)
|
||||
dataId = actor->GetNpcID();
|
||||
if (MannequinIds.Contains(dataId))
|
||||
{
|
||||
static ByteString Get(byte* ptr)
|
||||
=> ptr == null ? ByteString.Empty : new ByteString(ptr);
|
||||
|
||||
var retainerName = Get(actor->Name);
|
||||
var actualName = _framework.IsInFrameworkUpdateThread ? Get(actor->GetName()) : ByteString.Empty;
|
||||
if (!actualName.Equals(retainerName))
|
||||
{
|
||||
var ident = check
|
||||
? CreateRetainer(retainerName, ActorIdentifier.RetainerType.Mannequin)
|
||||
: CreateIndividualUnchecked(IdentifierType.Retainer, retainerName, (ushort)ActorIdentifier.RetainerType.Mannequin,
|
||||
ObjectKind.EventNpc, dataId);
|
||||
if (ident.IsValid)
|
||||
return ident;
|
||||
}
|
||||
}
|
||||
|
||||
var index = withoutIndex ? ushort.MaxValue : actor->ObjectIndex;
|
||||
return check
|
||||
? CreateNpc(ObjectKind.EventNpc, dataId, index)
|
||||
: CreateIndividualUnchecked(IdentifierType.Npc, ByteString.Empty, index, ObjectKind.EventNpc, dataId);
|
||||
}
|
||||
case ObjectKind.MountType:
|
||||
case ObjectKind.Companion:
|
||||
case ObjectKind.Ornament:
|
||||
{
|
||||
owner = HandleCutscene(
|
||||
(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_objects.GetObjectAddress(actor->ObjectIndex - 1));
|
||||
if (owner == null)
|
||||
return ActorIdentifier.Invalid;
|
||||
|
||||
var dataId = GetCompanionId(actor, (Character*)owner);
|
||||
var name = new ByteString(owner->Name);
|
||||
var homeWorld = ((Character*)owner)->HomeWorld;
|
||||
return check
|
||||
? CreateOwned(name, homeWorld, kind, dataId)
|
||||
: CreateIndividualUnchecked(IdentifierType.Owned, name, homeWorld, kind, dataId);
|
||||
}
|
||||
case ObjectKind.Retainer:
|
||||
{
|
||||
var name = new ByteString(actor->Name);
|
||||
return check
|
||||
? CreateRetainer(name, ActorIdentifier.RetainerType.Bell)
|
||||
: CreateIndividualUnchecked(IdentifierType.Retainer, name, (ushort)ActorIdentifier.RetainerType.Bell, ObjectKind.None,
|
||||
uint.MaxValue);
|
||||
}
|
||||
default:
|
||||
{
|
||||
var name = new ByteString(actor->Name);
|
||||
var index = withoutIndex ? ushort.MaxValue : actor->ObjectIndex;
|
||||
return CreateIndividualUnchecked(IdentifierType.UnkObject, name, index, ObjectKind.None, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtain the current companion ID for an object by its actor and owner.
|
||||
/// </summary>
|
||||
private unsafe uint GetCompanionId(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* actor,
|
||||
Character* owner)
|
||||
{
|
||||
return (ObjectKind)actor->ObjectKind switch
|
||||
{
|
||||
ObjectKind.MountType => owner->Mount.MountId,
|
||||
ObjectKind.Ornament => owner->Ornament.OrnamentId,
|
||||
ObjectKind.Companion => actor->DataID,
|
||||
_ => actor->DataID,
|
||||
};
|
||||
}
|
||||
|
||||
public unsafe ActorIdentifier FromObject(GameObject? actor, out FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* owner,
|
||||
bool allowPlayerNpc, bool check, bool withoutIndex)
|
||||
=> FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)(actor?.Address ?? IntPtr.Zero), out owner, allowPlayerNpc,
|
||||
check, withoutIndex);
|
||||
|
||||
public unsafe ActorIdentifier FromObject(GameObject? actor, bool allowPlayerNpc, bool check, bool withoutIndex)
|
||||
=> FromObject(actor, out _, allowPlayerNpc, check, withoutIndex);
|
||||
|
||||
public ActorIdentifier CreateIndividual(IdentifierType type, ByteString name, ushort homeWorld, ObjectKind kind, uint dataId)
|
||||
=> type switch
|
||||
{
|
||||
IdentifierType.Player => CreatePlayer(name, homeWorld),
|
||||
IdentifierType.Retainer => CreateRetainer(name, (ActorIdentifier.RetainerType)homeWorld),
|
||||
IdentifierType.Owned => CreateOwned(name, homeWorld, kind, dataId),
|
||||
IdentifierType.Special => CreateSpecial((ScreenActor)homeWorld),
|
||||
IdentifierType.Npc => CreateNpc(kind, dataId, homeWorld),
|
||||
IdentifierType.UnkObject => CreateIndividualUnchecked(IdentifierType.UnkObject, name, homeWorld, ObjectKind.None, 0),
|
||||
_ => ActorIdentifier.Invalid,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Only use this if you are sure the input is valid.
|
||||
/// </summary>
|
||||
public ActorIdentifier CreateIndividualUnchecked(IdentifierType type, ByteString name, ushort homeWorld, ObjectKind kind, uint dataId)
|
||||
=> new(type, kind, homeWorld, dataId, name);
|
||||
|
||||
public ActorIdentifier CreatePlayer(ByteString name, ushort homeWorld)
|
||||
{
|
||||
if (!VerifyWorld(homeWorld) || !VerifyPlayerName(name.Span))
|
||||
return ActorIdentifier.Invalid;
|
||||
|
||||
return new ActorIdentifier(IdentifierType.Player, ObjectKind.Player, homeWorld, 0, name);
|
||||
}
|
||||
|
||||
public ActorIdentifier CreateRetainer(ByteString name, ActorIdentifier.RetainerType type)
|
||||
{
|
||||
if (!VerifyRetainerName(name.Span))
|
||||
return ActorIdentifier.Invalid;
|
||||
|
||||
return new ActorIdentifier(IdentifierType.Retainer, ObjectKind.Retainer, (ushort)type, 0, name);
|
||||
}
|
||||
|
||||
public ActorIdentifier CreateSpecial(ScreenActor actor)
|
||||
{
|
||||
if (!VerifySpecial(actor))
|
||||
return ActorIdentifier.Invalid;
|
||||
|
||||
return new ActorIdentifier(IdentifierType.Special, ObjectKind.Player, (ushort)actor, 0, ByteString.Empty);
|
||||
}
|
||||
|
||||
public ActorIdentifier CreateNpc(ObjectKind kind, uint data, ushort index = ushort.MaxValue)
|
||||
{
|
||||
if (!VerifyIndex(index) || !VerifyNpcData(kind, data))
|
||||
return ActorIdentifier.Invalid;
|
||||
|
||||
return new ActorIdentifier(IdentifierType.Npc, kind, index, data, ByteString.Empty);
|
||||
}
|
||||
|
||||
public ActorIdentifier CreateOwned(ByteString ownerName, ushort homeWorld, ObjectKind kind, uint dataId)
|
||||
{
|
||||
if (!VerifyWorld(homeWorld) || !VerifyPlayerName(ownerName.Span) || !VerifyOwnedData(kind, dataId))
|
||||
return ActorIdentifier.Invalid;
|
||||
|
||||
return new ActorIdentifier(IdentifierType.Owned, kind, homeWorld, dataId, ownerName);
|
||||
}
|
||||
|
||||
/// <summary> Checks SE naming rules. </summary>
|
||||
public static bool VerifyPlayerName(ReadOnlySpan<byte> name)
|
||||
{
|
||||
// Total no more than 20 characters + space.
|
||||
if (name.Length is < 5 or > 21)
|
||||
return false;
|
||||
|
||||
// Forename and surname, no more spaces.
|
||||
var splitIndex = name.IndexOf((byte)' ');
|
||||
if (splitIndex < 0 || name[(splitIndex + 1)..].IndexOf((byte)' ') >= 0)
|
||||
return false;
|
||||
|
||||
return CheckNamePart(name[..splitIndex], 2, 15) && CheckNamePart(name[(splitIndex + 1)..], 2, 15);
|
||||
}
|
||||
|
||||
/// <summary> Checks SE naming rules. </summary>
|
||||
public static bool VerifyPlayerName(ReadOnlySpan<char> name)
|
||||
{
|
||||
// Total no more than 20 characters + space.
|
||||
if (name.Length is < 5 or > 21)
|
||||
return false;
|
||||
|
||||
// Forename and surname, no more spaces.
|
||||
var splitIndex = name.IndexOf(' ');
|
||||
if (splitIndex < 0 || name[(splitIndex + 1)..].IndexOf(' ') >= 0)
|
||||
return false;
|
||||
|
||||
return CheckNamePart(name[..splitIndex], 2, 15) && CheckNamePart(name[(splitIndex + 1)..], 2, 15);
|
||||
}
|
||||
|
||||
/// <summary> Checks SE naming rules. </summary>
|
||||
public static bool VerifyRetainerName(ReadOnlySpan<byte> name)
|
||||
=> CheckNamePart(name, 3, 20);
|
||||
|
||||
/// <summary> Checks SE naming rules. </summary>
|
||||
public static bool VerifyRetainerName(ReadOnlySpan<char> name)
|
||||
=> CheckNamePart(name, 3, 20);
|
||||
|
||||
private static bool CheckNamePart(ReadOnlySpan<char> part, int minLength, int maxLength)
|
||||
{
|
||||
// Each name part at least 2 and at most 15 characters for players, and at least 3 and at most 20 characters for retainers.
|
||||
if (part.Length < minLength || part.Length > maxLength)
|
||||
return false;
|
||||
|
||||
// Each part starting with capitalized letter.
|
||||
if (part[0] is < 'A' or > 'Z')
|
||||
return false;
|
||||
|
||||
// Every other symbol needs to be lowercase letter, hyphen or apostrophe.
|
||||
var last = '\0';
|
||||
for (var i = 1; i < part.Length; ++i)
|
||||
{
|
||||
var current = part[i];
|
||||
if (current is not ('\'' or '-' or (>= 'a' and <= 'z')))
|
||||
return false;
|
||||
|
||||
// Hyphens can not be used in succession, after or before apostrophes or as the last symbol.
|
||||
if (last is '\'' && current is '-')
|
||||
return false;
|
||||
if (last is '-' && current is '-' or '\'')
|
||||
return false;
|
||||
|
||||
last = current;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool CheckNamePart(ReadOnlySpan<byte> part, int minLength, int maxLength)
|
||||
{
|
||||
// Each name part at least 2 and at most 15 characters for players, and at least 3 and at most 20 characters for retainers.
|
||||
if (part.Length < minLength || part.Length > maxLength)
|
||||
return false;
|
||||
|
||||
// Each part starting with capitalized letter.
|
||||
if (part[0] is < (byte)'A' or > (byte)'Z')
|
||||
return false;
|
||||
|
||||
// Every other symbol needs to be lowercase letter, hyphen or apostrophe.
|
||||
var last = (byte)'\0';
|
||||
for (var i = 1; i < part.Length; ++i)
|
||||
{
|
||||
var current = part[i];
|
||||
if (current is not ((byte)'\'' or (byte)'-' or (>= (byte)'a' and <= (byte)'z')))
|
||||
return false;
|
||||
|
||||
// Hyphens can not be used in succession, after or before apostrophes or as the last symbol.
|
||||
if (last is (byte)'\'' && current is (byte)'-')
|
||||
return false;
|
||||
if (last is (byte)'-' && current is (byte)'-' or (byte)'\'')
|
||||
return false;
|
||||
|
||||
last = current;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Checks if the world is a valid public world or ushort.MaxValue (any world). </summary>
|
||||
public bool VerifyWorld(ushort 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(ScreenActor actor)
|
||||
=> actor is >= ScreenActor.CharacterScreen and <= ScreenActor.Card8;
|
||||
|
||||
/// <summary> Verify that the object index is a valid index for an NPC. </summary>
|
||||
public bool VerifyIndex(ushort index)
|
||||
{
|
||||
return index switch
|
||||
{
|
||||
ushort.MaxValue => true,
|
||||
< 200 => index % 2 == 0,
|
||||
> (ushort)ScreenActor.Card8 => index < _objects.Length,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary> Verify that the object kind is a valid owned object, and the corresponding data Id. </summary>
|
||||
public bool VerifyOwnedData(ObjectKind kind, uint dataId)
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
ObjectKind.MountType => Data.Mounts.ContainsKey(dataId),
|
||||
ObjectKind.Companion => Data.Companions.ContainsKey(dataId),
|
||||
ObjectKind.Ornament => Data.Ornaments.ContainsKey(dataId),
|
||||
ObjectKind.BattleNpc => Data.BNpcs.ContainsKey(dataId),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public bool VerifyNpcData(ObjectKind kind, uint dataId)
|
||||
=> kind switch
|
||||
{
|
||||
ObjectKind.MountType => Data.Mounts.ContainsKey(dataId),
|
||||
ObjectKind.Companion => Data.Companions.ContainsKey(dataId),
|
||||
ObjectKind.Ornament => Data.Ornaments.ContainsKey(dataId),
|
||||
ObjectKind.BattleNpc => Data.BNpcs.ContainsKey(dataId),
|
||||
ObjectKind.EventNpc => Data.ENpcs.ContainsKey(dataId),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using System.Runtime.InteropServices;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
|
||||
namespace Penumbra;
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public unsafe struct AgentBannerInterface
|
||||
{
|
||||
[FieldOffset( 0x0 )] public AgentInterface AgentInterface;
|
||||
[FieldOffset( 0x28 )] public BannerInterfaceStorage* Data;
|
||||
|
||||
public BannerInterfaceStorage.CharacterData* Character( int idx )
|
||||
=> idx switch
|
||||
{
|
||||
_ when Data == null => null,
|
||||
0 => &Data->Character1,
|
||||
1 => &Data->Character2,
|
||||
2 => &Data->Character3,
|
||||
3 => &Data->Character4,
|
||||
4 => &Data->Character5,
|
||||
5 => &Data->Character6,
|
||||
6 => &Data->Character7,
|
||||
7 => &Data->Character8,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct AgentBannerParty
|
||||
{
|
||||
public static AgentBannerParty* Instance() => ( AgentBannerParty* )Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId( AgentId.BannerParty );
|
||||
|
||||
[FieldOffset( 0x0 )] public AgentBannerInterface AgentBannerInterface;
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public unsafe struct AgentBannerMIP
|
||||
{
|
||||
public static AgentBannerMIP* Instance() => ( AgentBannerMIP* )Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId( AgentId.BannerMIP );
|
||||
[FieldOffset( 0x0 )] public AgentBannerInterface AgentBannerInterface;
|
||||
}
|
||||
|
||||
// Client::UI::Agent::AgentBannerInterface::Storage
|
||||
// destructed in Client::UI::Agent::AgentBannerInterface::dtor
|
||||
[StructLayout( LayoutKind.Explicit, Size = 0x3B30 )]
|
||||
public unsafe struct BannerInterfaceStorage
|
||||
{
|
||||
// vtable: 48 8D 05 ?? ?? ?? ?? 48 89 01 48 8B F9 7E
|
||||
// dtor: E8 ?? ?? ?? ?? 48 83 EF ?? 75 ?? BA ?? ?? ?? ?? 48 8B CE E8 ?? ?? ?? ?? 48 89 7D
|
||||
[StructLayout( LayoutKind.Explicit, Size = 0x760 )]
|
||||
public struct CharacterData
|
||||
{
|
||||
[FieldOffset( 0x000 )] public void** VTable;
|
||||
|
||||
[FieldOffset( 0x018 )] public Utf8String Name1;
|
||||
[FieldOffset( 0x080 )] public Utf8String Name2;
|
||||
[FieldOffset( 0x0E8 )] public Utf8String UnkString1;
|
||||
[FieldOffset( 0x150 )] public Utf8String UnkString2;
|
||||
[FieldOffset( 0x1C0 )] public Utf8String Job;
|
||||
[FieldOffset( 0x238 )] public uint WorldId;
|
||||
[FieldOffset( 0x240 )] public Utf8String UnkString3;
|
||||
|
||||
[FieldOffset( 0x2B0 )] public void* CharaView;
|
||||
[FieldOffset( 0x5D0 )] public AtkTexture AtkTexture;
|
||||
|
||||
[FieldOffset( 0x6E0 )] public Utf8String Title;
|
||||
[FieldOffset( 0x750 )] public void* SomePointer;
|
||||
|
||||
}
|
||||
|
||||
[FieldOffset( 0x0000 )] public void* Agent; // AgentBannerParty, maybe other Banner agents
|
||||
[FieldOffset( 0x0008 )] public UIModule* UiModule;
|
||||
[FieldOffset( 0x0010 )] public uint Unk1; // Maybe count or bitfield, but probably not
|
||||
[FieldOffset( 0x0014 )] public uint Unk2;
|
||||
|
||||
[FieldOffset( 0x0020 )] public CharacterData Character1;
|
||||
[FieldOffset( 0x0780 )] public CharacterData Character2;
|
||||
[FieldOffset( 0x0EE0 )] public CharacterData Character3;
|
||||
[FieldOffset( 0x1640 )] public CharacterData Character4;
|
||||
[FieldOffset( 0x1DA0 )] public CharacterData Character5;
|
||||
[FieldOffset( 0x2500 )] public CharacterData Character6;
|
||||
[FieldOffset( 0x2C60 )] public CharacterData Character7;
|
||||
[FieldOffset( 0x33C0 )] public CharacterData Character8;
|
||||
|
||||
[FieldOffset( 0x3B20 )] public long Unk3;
|
||||
[FieldOffset( 0x3B28 )] public long Unk4;
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
namespace Penumbra.GameData.Actors;
|
||||
|
||||
public enum IdentifierType : byte
|
||||
{
|
||||
Invalid,
|
||||
Player,
|
||||
Owned,
|
||||
Special,
|
||||
Npc,
|
||||
Retainer,
|
||||
UnkObject,
|
||||
};
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
namespace Penumbra.GameData.Actors;
|
||||
|
||||
public enum ScreenActor : ushort
|
||||
{
|
||||
CutsceneStart = 200,
|
||||
GPosePlayer = 201,
|
||||
CutsceneEnd = 240,
|
||||
CharacterScreen = CutsceneEnd,
|
||||
ExamineScreen = 241,
|
||||
FittingRoom = 242,
|
||||
DyePreview = 243,
|
||||
Portrait = 244,
|
||||
Card6 = 245,
|
||||
Card7 = 246,
|
||||
Card8 = 247,
|
||||
ScreenEnd = Card8 + 1,
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,92 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud;
|
||||
using Dalamud.Logging;
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
protected virtual void DisposeInternal()
|
||||
{ }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
DisposeInternal();
|
||||
GC.SuppressFinalize(this);
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
~DataSharer()
|
||||
=> Dispose();
|
||||
|
||||
protected void DisposeTag(string tag)
|
||||
=> PluginInterface.RelinquishData(GetVersionedTag(tag, Language, Version));
|
||||
|
||||
protected T TryCatchData<T>(string tag, Func<T> func) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
return PluginInterface.GetOrCreateData(GetVersionedTag(tag, Language, Version), func);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error($"Error creating shared data for {tag}:\n{ex}");
|
||||
return func();
|
||||
}
|
||||
}
|
||||
|
||||
protected Task<T> TryCatchDataAsync<T>(string tag, Action<T> fill) where T : class, new()
|
||||
{
|
||||
tag = GetVersionedTag(tag, Language, Version);
|
||||
if (PluginInterface.TryGetData<T>(tag, out var data))
|
||||
return Task.FromResult(data);
|
||||
|
||||
T ret = new();
|
||||
return Task.Run(() =>
|
||||
{
|
||||
fill(ret);
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
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}";
|
||||
}
|
||||
|
|
@ -1,463 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
public partial class DisassembledShader
|
||||
{
|
||||
public struct ResourceBinding
|
||||
{
|
||||
public string Name;
|
||||
public ResourceType Type;
|
||||
public Format Format;
|
||||
public ResourceDimension Dimension;
|
||||
public uint Slot;
|
||||
public uint Elements;
|
||||
public uint RegisterCount;
|
||||
public VectorComponents[] Used;
|
||||
public VectorComponents UsedDynamically;
|
||||
}
|
||||
|
||||
// Abbreviated using the uppercased first char of their name
|
||||
public enum ResourceType : byte
|
||||
{
|
||||
Unspecified = 0,
|
||||
ConstantBuffer = 0x43, // 'C'
|
||||
Sampler = 0x53, // 'S'
|
||||
Texture = 0x54, // 'T'
|
||||
Uav = 0x55, // 'U'
|
||||
}
|
||||
|
||||
// Abbreviated using the uppercased first and last char of their name
|
||||
public enum Format : ushort
|
||||
{
|
||||
Unspecified = 0,
|
||||
NotApplicable = 0x4E41, // 'NA'
|
||||
Int = 0x4954, // 'IT'
|
||||
Int4 = 0x4934, // 'I4'
|
||||
Float = 0x4654, // 'FT'
|
||||
Float4 = 0x4634, // 'F4'
|
||||
}
|
||||
|
||||
// Abbreviated using the uppercased first and last char of their name
|
||||
public enum ResourceDimension : ushort
|
||||
{
|
||||
Unspecified = 0,
|
||||
NotApplicable = 0x4E41, // 'NA'
|
||||
TwoD = 0x3244, // '2D'
|
||||
ThreeD = 0x3344, // '3D'
|
||||
Cube = 0x4345, // 'CE'
|
||||
}
|
||||
|
||||
public struct InputOutput
|
||||
{
|
||||
public string Name;
|
||||
public uint Index;
|
||||
public VectorComponents Mask;
|
||||
public uint Register;
|
||||
public string SystemValue;
|
||||
public Format Format;
|
||||
public VectorComponents Used;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum VectorComponents : byte
|
||||
{
|
||||
X = 1,
|
||||
Y = 2,
|
||||
Z = 4,
|
||||
W = 8,
|
||||
All = 15,
|
||||
}
|
||||
|
||||
public enum ShaderStage : byte
|
||||
{
|
||||
Unspecified = 0,
|
||||
Pixel = 0x50, // 'P'
|
||||
Vertex = 0x56, // 'V'
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"\s(\w+)(?:\[\d+\])?;\s*//\s*Offset:\s*0\s*Size:\s*(\d+)$", RegexOptions.Multiline | RegexOptions.NonBacktracking)]
|
||||
private static partial Regex ResourceBindingSizeRegex();
|
||||
|
||||
[GeneratedRegex(@"c(\d+)(?:\[([^\]]+)\])?(?:\.([wxyz]+))?", RegexOptions.NonBacktracking)]
|
||||
private static partial Regex Sm3ConstantBufferUsageRegex();
|
||||
|
||||
[GeneratedRegex(@"^\s*texld\S*\s+[^,]+,[^,]+,\s*s(\d+)", RegexOptions.NonBacktracking)]
|
||||
private static partial Regex Sm3TextureUsageRegex();
|
||||
|
||||
[GeneratedRegex(@"cb(\d+)\[([^\]]+)\]\.([wxyz]+)", RegexOptions.NonBacktracking)]
|
||||
private static partial Regex Sm5ConstantBufferUsageRegex();
|
||||
|
||||
[GeneratedRegex(@"^\s*sample_\S*\s+[^.]+\.([wxyz]+),[^,]+,\s*t(\d+)\.([wxyz]+)", RegexOptions.NonBacktracking)]
|
||||
private static partial Regex Sm5TextureUsageRegex();
|
||||
|
||||
private static readonly char[] Digits = Enumerable.Range(0, 10).Select(c => (char)('0' + c)).ToArray();
|
||||
|
||||
public readonly ByteString RawDisassembly;
|
||||
public readonly uint ShaderModel;
|
||||
public readonly ShaderStage Stage;
|
||||
public readonly string BufferDefinitions;
|
||||
public readonly ResourceBinding[] ResourceBindings;
|
||||
public readonly InputOutput[] InputSignature;
|
||||
public readonly InputOutput[] OutputSignature;
|
||||
public readonly IReadOnlyList<ByteString> Instructions;
|
||||
|
||||
public DisassembledShader(ByteString rawDisassembly)
|
||||
{
|
||||
RawDisassembly = rawDisassembly;
|
||||
var lines = rawDisassembly.Split((byte) '\n');
|
||||
Instructions = lines.FindAll(ln => !ln.StartsWith("//"u8) && ln.Length > 0);
|
||||
var shaderModel = Instructions[0].Trim().Split((byte) '_');
|
||||
Stage = (ShaderStage)(byte)char.ToUpper((char) shaderModel[0][0]);
|
||||
ShaderModel = (uint.Parse(shaderModel[1].ToString()) << 8) | uint.Parse(shaderModel[2].ToString());
|
||||
var header = PreParseHeader(lines.Take(lines.IndexOf(Instructions[0])).Select(l => l.ToString()).ToArray());
|
||||
switch (ShaderModel >> 8)
|
||||
{
|
||||
case 3:
|
||||
ParseSm3Header(header, out BufferDefinitions, out ResourceBindings, out InputSignature, out OutputSignature);
|
||||
ParseSm3ResourceUsage(Instructions, ResourceBindings);
|
||||
break;
|
||||
case 5:
|
||||
ParseSm5Header(header, out BufferDefinitions, out ResourceBindings, out InputSignature, out OutputSignature);
|
||||
ParseSm5ResourceUsage(Instructions, ResourceBindings);
|
||||
break;
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public ResourceBinding? GetResourceBindingByName(ResourceType type, string name)
|
||||
=> ResourceBindings.FirstOrNull(b => b.Type == type && b.Name == name);
|
||||
|
||||
public ResourceBinding? GetResourceBindingBySlot(ResourceType type, uint slot)
|
||||
=> ResourceBindings.FirstOrNull(b => b.Type == type && b.Slot == slot);
|
||||
|
||||
public static DisassembledShader Disassemble(ReadOnlySpan<byte> shaderBlob)
|
||||
=> new(D3DCompiler.Disassemble(shaderBlob));
|
||||
|
||||
private static void ParseSm3Header(Dictionary<string, string[]> header, out string bufferDefinitions,
|
||||
out ResourceBinding[] resourceBindings, out InputOutput[] inputSignature, out InputOutput[] outputSignature)
|
||||
{
|
||||
bufferDefinitions = header.TryGetValue("Parameters", out var rawParameters)
|
||||
? string.Join('\n', rawParameters)
|
||||
: string.Empty;
|
||||
if (header.TryGetValue("Registers", out var rawRegisters))
|
||||
{
|
||||
var (_, registers) = ParseTable(rawRegisters);
|
||||
resourceBindings = Array.ConvertAll(registers, register =>
|
||||
{
|
||||
var type = (ResourceType)(byte)char.ToUpper(register[1][0]);
|
||||
if (type == ResourceType.Sampler)
|
||||
type = ResourceType.Texture;
|
||||
var size = uint.Parse(register[2]);
|
||||
return new ResourceBinding
|
||||
{
|
||||
Name = register[0],
|
||||
Type = type,
|
||||
Format = Format.Unspecified,
|
||||
Dimension = ResourceDimension.Unspecified,
|
||||
Slot = uint.Parse(register[1][1..]),
|
||||
Elements = 1,
|
||||
RegisterCount = size,
|
||||
Used = new VectorComponents[size],
|
||||
};
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
resourceBindings = Array.Empty<ResourceBinding>();
|
||||
}
|
||||
|
||||
inputSignature = Array.Empty<InputOutput>();
|
||||
outputSignature = Array.Empty<InputOutput>();
|
||||
}
|
||||
|
||||
private static void ParseSm3ResourceUsage(IReadOnlyList<ByteString> instructions, ResourceBinding[] resourceBindings)
|
||||
{
|
||||
var cbIndices = new Dictionary<uint, int>();
|
||||
var tIndices = new Dictionary<uint, int>();
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var binding in resourceBindings)
|
||||
{
|
||||
switch (binding.Type)
|
||||
{
|
||||
case ResourceType.ConstantBuffer:
|
||||
for (var j = 0u; j < binding.RegisterCount; j++)
|
||||
cbIndices[binding.Slot + j] = i;
|
||||
break;
|
||||
case ResourceType.Texture:
|
||||
tIndices[binding.Slot] = i;
|
||||
break;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
var trimmed = instruction.Trim();
|
||||
if (trimmed.StartsWith("def"u8) || trimmed.StartsWith("dcl"u8))
|
||||
continue;
|
||||
|
||||
var instructionString = instruction.ToString();
|
||||
foreach (Match cbMatch in Sm3ConstantBufferUsageRegex().Matches(instructionString))
|
||||
{
|
||||
var buffer = uint.Parse(cbMatch.Groups[1].Value);
|
||||
if (cbIndices.TryGetValue(buffer, out var i))
|
||||
{
|
||||
var swizzle = cbMatch.Groups[3].Success ? ParseVectorComponents(cbMatch.Groups[3].Value) : VectorComponents.All;
|
||||
if (cbMatch.Groups[2].Success)
|
||||
resourceBindings[i].UsedDynamically |= swizzle;
|
||||
else
|
||||
resourceBindings[i].Used[buffer - resourceBindings[i].Slot] |= swizzle;
|
||||
}
|
||||
}
|
||||
|
||||
var tMatch = Sm3TextureUsageRegex().Match(instructionString);
|
||||
if (tMatch.Success)
|
||||
{
|
||||
var texture = uint.Parse(tMatch.Groups[1].Value);
|
||||
if (tIndices.TryGetValue(texture, out var i))
|
||||
resourceBindings[i].Used[0] = VectorComponents.All;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParseSm5Header(Dictionary<string, string[]> header, out string bufferDefinitions,
|
||||
out ResourceBinding[] resourceBindings, out InputOutput[] inputSignature, out InputOutput[] outputSignature)
|
||||
{
|
||||
if (header.TryGetValue("Resource Bindings", out var rawResBindings))
|
||||
{
|
||||
var (head, resBindings) = ParseTable(rawResBindings);
|
||||
resourceBindings = Array.ConvertAll(resBindings, binding =>
|
||||
{
|
||||
var type = (ResourceType)(byte)char.ToUpper(binding[1][0]);
|
||||
return new ResourceBinding
|
||||
{
|
||||
Name = binding[0],
|
||||
Type = type,
|
||||
Format = (Format)(((byte)char.ToUpper(binding[2][0]) << 8) | (byte)char.ToUpper(binding[2][^1])),
|
||||
Dimension = (ResourceDimension)(((byte)char.ToUpper(binding[3][0]) << 8) | (byte)char.ToUpper(binding[3][^1])),
|
||||
Slot = uint.Parse(binding[4][binding[4].IndexOfAny(Digits)..]),
|
||||
Elements = uint.Parse(binding[5]),
|
||||
RegisterCount = type == ResourceType.Texture ? 1u : 0u,
|
||||
Used = type == ResourceType.Texture ? new VectorComponents[1] : Array.Empty<VectorComponents>(),
|
||||
};
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
resourceBindings = Array.Empty<ResourceBinding>();
|
||||
}
|
||||
|
||||
if (header.TryGetValue("Buffer Definitions", out var rawBufferDefs))
|
||||
{
|
||||
bufferDefinitions = string.Join('\n', rawBufferDefs);
|
||||
foreach (Match match in ResourceBindingSizeRegex().Matches(bufferDefinitions))
|
||||
{
|
||||
var name = match.Groups[1].Value;
|
||||
var bytesSize = uint.Parse(match.Groups[2].Value);
|
||||
var pos = Array.FindIndex(resourceBindings, binding => binding.Type == ResourceType.ConstantBuffer && binding.Name == name);
|
||||
if (pos >= 0)
|
||||
{
|
||||
resourceBindings[pos].RegisterCount = (bytesSize + 0xF) >> 4;
|
||||
resourceBindings[pos].Used = new VectorComponents[resourceBindings[pos].RegisterCount];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferDefinitions = string.Empty;
|
||||
}
|
||||
|
||||
static InputOutput ParseInputOutput(string[] inOut)
|
||||
=> new()
|
||||
{
|
||||
Name = inOut[0],
|
||||
Index = uint.Parse(inOut[1]),
|
||||
Mask = ParseVectorComponents(inOut[2]),
|
||||
Register = uint.Parse(inOut[3]),
|
||||
SystemValue = string.Intern(inOut[4]),
|
||||
Format = (Format)(((byte)char.ToUpper(inOut[5][0]) << 8) | (byte)char.ToUpper(inOut[5][^1])),
|
||||
Used = ParseVectorComponents(inOut[6]),
|
||||
};
|
||||
|
||||
if (header.TryGetValue("Input signature", out var rawInputSig))
|
||||
{
|
||||
var (_, inputSig) = ParseTable(rawInputSig);
|
||||
inputSignature = Array.ConvertAll(inputSig, ParseInputOutput);
|
||||
}
|
||||
else
|
||||
{
|
||||
inputSignature = Array.Empty<InputOutput>();
|
||||
}
|
||||
|
||||
if (header.TryGetValue("Output signature", out var rawOutputSig))
|
||||
{
|
||||
var (_, outputSig) = ParseTable(rawOutputSig);
|
||||
outputSignature = Array.ConvertAll(outputSig, ParseInputOutput);
|
||||
}
|
||||
else
|
||||
{
|
||||
outputSignature = Array.Empty<InputOutput>();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParseSm5ResourceUsage(IReadOnlyList<ByteString> instructions, ResourceBinding[] resourceBindings)
|
||||
{
|
||||
var cbIndices = new Dictionary<uint, int>();
|
||||
var tIndices = new Dictionary<uint, int>();
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var binding in resourceBindings)
|
||||
{
|
||||
switch (binding.Type)
|
||||
{
|
||||
case ResourceType.ConstantBuffer:
|
||||
cbIndices[binding.Slot] = i;
|
||||
break;
|
||||
case ResourceType.Texture:
|
||||
tIndices[binding.Slot] = i;
|
||||
break;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
var trimmed = instruction.Trim();
|
||||
if (trimmed.StartsWith("def"u8) || trimmed.StartsWith("dcl"u8))
|
||||
continue;
|
||||
|
||||
var instructionString = instruction.ToString();
|
||||
foreach (Match cbMatch in Sm5ConstantBufferUsageRegex().Matches(instructionString))
|
||||
{
|
||||
var buffer = uint.Parse(cbMatch.Groups[1].Value);
|
||||
if (cbIndices.TryGetValue(buffer, out var i))
|
||||
{
|
||||
var swizzle = ParseVectorComponents(cbMatch.Groups[3].Value);
|
||||
if (int.TryParse(cbMatch.Groups[2].Value, out var vector))
|
||||
{
|
||||
if (vector < resourceBindings[i].Used.Length)
|
||||
resourceBindings[i].Used[vector] |= swizzle;
|
||||
}
|
||||
else
|
||||
{
|
||||
resourceBindings[i].UsedDynamically |= swizzle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tMatch = Sm5TextureUsageRegex().Match(instructionString);
|
||||
if (tMatch.Success)
|
||||
{
|
||||
var texture = uint.Parse(tMatch.Groups[2].Value);
|
||||
if (tIndices.TryGetValue(texture, out var i))
|
||||
{
|
||||
var outSwizzle = ParseVectorComponents(tMatch.Groups[1].Value);
|
||||
var rawInSwizzle = tMatch.Groups[3].Value;
|
||||
var inSwizzle = new StringBuilder(4);
|
||||
if ((outSwizzle & VectorComponents.X) != 0)
|
||||
inSwizzle.Append(rawInSwizzle[0]);
|
||||
if ((outSwizzle & VectorComponents.Y) != 0)
|
||||
inSwizzle.Append(rawInSwizzle[1]);
|
||||
if ((outSwizzle & VectorComponents.Z) != 0)
|
||||
inSwizzle.Append(rawInSwizzle[2]);
|
||||
if ((outSwizzle & VectorComponents.W) != 0)
|
||||
inSwizzle.Append(rawInSwizzle[3]);
|
||||
resourceBindings[i].Used[0] |= ParseVectorComponents(inSwizzle.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static VectorComponents ParseVectorComponents(string components)
|
||||
{
|
||||
components = components.ToUpperInvariant();
|
||||
return (components.Contains('X') ? VectorComponents.X : 0)
|
||||
| (components.Contains('Y') ? VectorComponents.Y : 0)
|
||||
| (components.Contains('Z') ? VectorComponents.Z : 0)
|
||||
| (components.Contains('W') ? VectorComponents.W : 0);
|
||||
}
|
||||
|
||||
private static Dictionary<string, string[]> PreParseHeader(ReadOnlySpan<string> header)
|
||||
{
|
||||
var sections = new Dictionary<string, string[]>();
|
||||
|
||||
void AddSection(string name, ReadOnlySpan<string> section)
|
||||
{
|
||||
while (section.Length > 0 && section[0].Length <= 3)
|
||||
section = section[1..];
|
||||
while (section.Length > 0 && section[^1].Length <= 3)
|
||||
section = section[..^1];
|
||||
sections.Add(name, Array.ConvertAll(section.ToArray(), ln => ln.Length <= 3 ? string.Empty : ln[3..]));
|
||||
}
|
||||
|
||||
var lastSectionName = "";
|
||||
var lastSectionStart = 0;
|
||||
for (var i = 1; i < header.Length - 1; ++i)
|
||||
{
|
||||
string current;
|
||||
if (header[i - 1].Length <= 3 && header[i + 1].Length <= 3 && (current = header[i].TrimEnd()).EndsWith(':'))
|
||||
{
|
||||
AddSection(lastSectionName, header[lastSectionStart..(i - 1)]);
|
||||
lastSectionName = current[3..^1];
|
||||
lastSectionStart = i + 2;
|
||||
++i; // The next line cannot match
|
||||
}
|
||||
}
|
||||
|
||||
AddSection(lastSectionName, header[lastSectionStart..]);
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
private static (string[], string[][]) ParseTable(ReadOnlySpan<string> lines)
|
||||
{
|
||||
var columns = new List<Range>();
|
||||
{
|
||||
var dashLine = lines[1];
|
||||
for (var i = 0; true; /* this part intentionally left blank */)
|
||||
{
|
||||
var start = dashLine.IndexOf('-', i);
|
||||
if (start < 0)
|
||||
break;
|
||||
|
||||
var end = dashLine.IndexOf(' ', start + 1);
|
||||
if (end < 0)
|
||||
{
|
||||
columns.Add(start..dashLine.Length);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
columns.Add(start..end);
|
||||
i = end + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
var headers = new string[columns.Count];
|
||||
{
|
||||
var headerLine = lines[0];
|
||||
for (var i = 0; i < columns.Count; ++i)
|
||||
headers[i] = headerLine[columns[i]].Trim();
|
||||
}
|
||||
var data = new List<string[]>();
|
||||
foreach (var line in lines[2..])
|
||||
{
|
||||
var row = new string[columns.Count];
|
||||
for (var i = 0; i < columns.Count; ++i)
|
||||
row[i] = line[columns[i]].Trim();
|
||||
data.Add(row);
|
||||
}
|
||||
|
||||
return (headers, data.ToArray());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Plugin;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using PseudoEquipItem = System.ValueTuple<string, ulong, ushort, ushort, ushort, byte, byte>;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
internal sealed class EquipmentIdentificationList : KeyList<PseudoEquipItem>
|
||||
{
|
||||
private const string Tag = "EquipmentIdentification";
|
||||
|
||||
public EquipmentIdentificationList(DalamudPluginInterface pi, ClientLanguage language, ItemData data)
|
||||
: base(pi, Tag, language, ObjectIdentification.IdentificationVersion, CreateEquipmentList(data))
|
||||
{ }
|
||||
|
||||
public IEnumerable<EquipItem> 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)).Select(e => (EquipItem)e);
|
||||
if (variant == 0)
|
||||
return Between(ToKey(modelId, slot, 0), ToKey(modelId, slot, 0xFF)).Select(e => (EquipItem)e);
|
||||
|
||||
return Between(ToKey(modelId, slot, variant), ToKey(modelId, slot, variant)).Select(e => (EquipItem)e);
|
||||
}
|
||||
|
||||
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(EquipItem i)
|
||||
=> ToKey(i.ModelId, i.Type.ToSlot(), i.Variant);
|
||||
|
||||
protected override IEnumerable<ulong> ToKeys(PseudoEquipItem i)
|
||||
{
|
||||
yield return ToKey(i);
|
||||
}
|
||||
|
||||
protected override bool ValidKey(ulong key)
|
||||
=> key != 0;
|
||||
|
||||
protected override int ValueKeySelector(PseudoEquipItem data)
|
||||
=> (int)data.Item2;
|
||||
|
||||
private static IEnumerable<PseudoEquipItem> CreateEquipmentList(ItemData data)
|
||||
{
|
||||
return data.Where(kvp => kvp.Key.IsEquipment() || kvp.Key.IsAccessory())
|
||||
.SelectMany(kvp => kvp.Value)
|
||||
.Select(i => (PseudoEquipItem)i)
|
||||
.Concat(CustomList);
|
||||
}
|
||||
|
||||
private static IEnumerable<PseudoEquipItem> CustomList
|
||||
=> new[]
|
||||
{
|
||||
(PseudoEquipItem)EquipItem.FromIds(0, 0, (SetId)8100, (WeaponType)0, 01, FullEquipType.Body, "Reaper Shroud"),
|
||||
(PseudoEquipItem)EquipItem.FromIds(0, 0, (SetId)9041, (WeaponType)0, 01, FullEquipType.Head, "Cid's Bandana (9041)"),
|
||||
(PseudoEquipItem)EquipItem.FromIds(0, 0, (SetId)9041, (WeaponType)0, 01, FullEquipType.Body, "Cid's Body (9041)"),
|
||||
(PseudoEquipItem)EquipItem.FromIds(0, 0, (SetId)9903, (WeaponType)0, 01, FullEquipType.Head, "Smallclothes (NPC, 9903)"),
|
||||
(PseudoEquipItem)EquipItem.FromIds(0, 0, (SetId)9903, (WeaponType)0, 01, FullEquipType.Body, "Smallclothes (NPC, 9903)"),
|
||||
(PseudoEquipItem)EquipItem.FromIds(0, 0, (SetId)9903, (WeaponType)0, 01, FullEquipType.Hands, "Smallclothes (NPC, 9903)"),
|
||||
(PseudoEquipItem)EquipItem.FromIds(0, 0, (SetId)9903, (WeaponType)0, 01, FullEquipType.Legs, "Smallclothes (NPC, 9903)"),
|
||||
(PseudoEquipItem)EquipItem.FromIds(0, 0, (SetId)9903, (WeaponType)0, 01, FullEquipType.Feet, "Smallclothes (NPC, 9903)"),
|
||||
(PseudoEquipItem)EquipItem.FromIds(0, 0, (SetId)9212, (WeaponType)0, 12, FullEquipType.Body, "Ancient Robes (Lahabrea)"),
|
||||
(PseudoEquipItem)EquipItem.FromIds(0, 0, (SetId)9212, (WeaponType)0, 01, FullEquipType.Legs, "Ancient Legs"),
|
||||
(PseudoEquipItem)EquipItem.FromIds(0, 0, (SetId)9212, (WeaponType)0, 01, FullEquipType.Feet, "Ancient Shoes"),
|
||||
};
|
||||
}
|
||||
|
|
@ -1,306 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
public class GamePathParser : IGamePathParser
|
||||
{
|
||||
/// <summary> Obtain basic information about a file path. </summary>
|
||||
public GameObjectInfo GetFileInfo(string path)
|
||||
{
|
||||
path = path.ToLowerInvariant().Replace('\\', '/');
|
||||
|
||||
var (fileType, objectType, match) = ParseGamePath(path);
|
||||
if (match is not { Success: true })
|
||||
return new GameObjectInfo
|
||||
{
|
||||
FileType = fileType,
|
||||
ObjectType = objectType,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var groups = match.Groups;
|
||||
switch (objectType)
|
||||
{
|
||||
case ObjectType.Accessory: return HandleEquipment(fileType, groups);
|
||||
case ObjectType.Equipment: return HandleEquipment(fileType, groups);
|
||||
case ObjectType.Weapon: return HandleWeapon(fileType, groups);
|
||||
case ObjectType.Map: return HandleMap(fileType, groups);
|
||||
case ObjectType.Monster: return HandleMonster(fileType, groups);
|
||||
case ObjectType.DemiHuman: return HandleDemiHuman(fileType, groups);
|
||||
case ObjectType.Character: return HandleCustomization(fileType, groups);
|
||||
case ObjectType.Icon: return HandleIcon(fileType, groups);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Could not parse {path}:\n{e}");
|
||||
}
|
||||
|
||||
return new GameObjectInfo
|
||||
{
|
||||
FileType = fileType,
|
||||
ObjectType = objectType,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary> Get the key of a VFX symbol. </summary>
|
||||
/// <returns>The lower-case key or an empty string if no match is found.</returns>
|
||||
public string VfxToKey(string path)
|
||||
{
|
||||
var match = GamePaths.Vfx.Tmb().Match(path);
|
||||
if (match.Success)
|
||||
return match.Groups["key"].Value.ToLowerInvariant();
|
||||
|
||||
match = GamePaths.Vfx.Pap().Match(path);
|
||||
return match.Success ? match.Groups["key"].Value.ToLowerInvariant() : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary> Obtain the ObjectType from a given path.</summary>
|
||||
public ObjectType PathToObjectType(string path)
|
||||
{
|
||||
if (path.Length == 0)
|
||||
return ObjectType.Unknown;
|
||||
|
||||
var folders = path.Split('/');
|
||||
if (folders.Length < 2)
|
||||
return ObjectType.Unknown;
|
||||
|
||||
return folders[0] switch
|
||||
{
|
||||
CharacterFolder => folders[1] switch
|
||||
{
|
||||
EquipmentFolder => ObjectType.Equipment,
|
||||
AccessoryFolder => ObjectType.Accessory,
|
||||
WeaponFolder => ObjectType.Weapon,
|
||||
PlayerFolder => ObjectType.Character,
|
||||
DemiHumanFolder => ObjectType.DemiHuman,
|
||||
MonsterFolder => ObjectType.Monster,
|
||||
CommonFolder => ObjectType.Character,
|
||||
_ => ObjectType.Unknown,
|
||||
},
|
||||
UiFolder => folders[1] switch
|
||||
{
|
||||
IconFolder => ObjectType.Icon,
|
||||
LoadingFolder => ObjectType.LoadingScreen,
|
||||
MapFolder => ObjectType.Map,
|
||||
InterfaceFolder => ObjectType.Interface,
|
||||
_ => ObjectType.Unknown,
|
||||
},
|
||||
CommonFolder => folders[1] switch
|
||||
{
|
||||
FontFolder => ObjectType.Font,
|
||||
_ => ObjectType.Unknown,
|
||||
},
|
||||
HousingFolder => ObjectType.Housing,
|
||||
WorldFolder1 => folders[1] switch
|
||||
{
|
||||
HousingFolder => ObjectType.Housing,
|
||||
_ => ObjectType.World,
|
||||
},
|
||||
WorldFolder2 => ObjectType.World,
|
||||
VfxFolder => ObjectType.Vfx,
|
||||
_ => ObjectType.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
private const string CharacterFolder = "chara";
|
||||
private const string EquipmentFolder = "equipment";
|
||||
private const string PlayerFolder = "human";
|
||||
private const string WeaponFolder = "weapon";
|
||||
private const string AccessoryFolder = "accessory";
|
||||
private const string DemiHumanFolder = "demihuman";
|
||||
private const string MonsterFolder = "monster";
|
||||
private const string CommonFolder = "common";
|
||||
private const string UiFolder = "ui";
|
||||
private const string IconFolder = "icon";
|
||||
private const string LoadingFolder = "loadingimage";
|
||||
private const string MapFolder = "map";
|
||||
private const string InterfaceFolder = "uld";
|
||||
private const string FontFolder = "font";
|
||||
private const string HousingFolder = "hou";
|
||||
private const string VfxFolder = "vfx";
|
||||
private const string WorldFolder1 = "bgcommon";
|
||||
private const string WorldFolder2 = "bg";
|
||||
|
||||
private (FileType, ObjectType, Match?) ParseGamePath(string path)
|
||||
{
|
||||
if (!Names.ExtensionToFileType.TryGetValue(Path.GetExtension(path), out var fileType))
|
||||
fileType = FileType.Unknown;
|
||||
|
||||
var objectType = PathToObjectType(path);
|
||||
|
||||
static Match TestCharacterTextures(string path)
|
||||
{
|
||||
var regexes = new Regex[]
|
||||
{
|
||||
GamePaths.Character.Tex.Regex(),
|
||||
GamePaths.Character.Tex.FolderRegex(),
|
||||
GamePaths.Character.Tex.SkinRegex(),
|
||||
GamePaths.Character.Tex.CatchlightRegex(),
|
||||
GamePaths.Character.Tex.DecalRegex(),
|
||||
};
|
||||
foreach (var regex in regexes)
|
||||
{
|
||||
var match = regex.Match(path);
|
||||
if (match.Success)
|
||||
return match;
|
||||
}
|
||||
|
||||
return Match.Empty;
|
||||
}
|
||||
|
||||
var match = (fileType, objectType) switch
|
||||
{
|
||||
(FileType.Font, ObjectType.Font) => GamePaths.Font.Regex().Match(path),
|
||||
(FileType.Imc, ObjectType.Weapon) => GamePaths.Weapon.Imc.Regex().Match(path),
|
||||
(FileType.Imc, ObjectType.Monster) => GamePaths.Monster.Imc.Regex().Match(path),
|
||||
(FileType.Imc, ObjectType.DemiHuman) => GamePaths.DemiHuman.Imc.Regex().Match(path),
|
||||
(FileType.Imc, ObjectType.Equipment) => GamePaths.Equipment.Imc.Regex().Match(path),
|
||||
(FileType.Imc, ObjectType.Accessory) => GamePaths.Accessory.Imc.Regex().Match(path),
|
||||
(FileType.Model, ObjectType.Weapon) => GamePaths.Weapon.Mdl.Regex().Match(path),
|
||||
(FileType.Model, ObjectType.Monster) => GamePaths.Monster.Mdl.Regex().Match(path),
|
||||
(FileType.Model, ObjectType.DemiHuman) => GamePaths.DemiHuman.Mdl.Regex().Match(path),
|
||||
(FileType.Model, ObjectType.Equipment) => GamePaths.Equipment.Mdl.Regex().Match(path),
|
||||
(FileType.Model, ObjectType.Accessory) => GamePaths.Accessory.Mdl.Regex().Match(path),
|
||||
(FileType.Model, ObjectType.Character) => GamePaths.Character.Mdl.Regex().Match(path),
|
||||
(FileType.Material, ObjectType.Weapon) => GamePaths.Weapon.Mtrl.Regex().Match(path),
|
||||
(FileType.Material, ObjectType.Monster) => GamePaths.Monster.Mtrl.Regex().Match(path),
|
||||
(FileType.Material, ObjectType.DemiHuman) => GamePaths.DemiHuman.Mtrl.Regex().Match(path),
|
||||
(FileType.Material, ObjectType.Equipment) => GamePaths.Equipment.Mtrl.Regex().Match(path),
|
||||
(FileType.Material, ObjectType.Accessory) => GamePaths.Accessory.Mtrl.Regex().Match(path),
|
||||
(FileType.Material, ObjectType.Character) => GamePaths.Character.Mtrl.Regex().Match(path),
|
||||
(FileType.Texture, ObjectType.Weapon) => GamePaths.Weapon.Tex.Regex().Match(path),
|
||||
(FileType.Texture, ObjectType.Monster) => GamePaths.Monster.Tex.Regex().Match(path),
|
||||
(FileType.Texture, ObjectType.DemiHuman) => GamePaths.DemiHuman.Tex.Regex().Match(path),
|
||||
(FileType.Texture, ObjectType.Equipment) => GamePaths.Equipment.Tex.Regex().Match(path),
|
||||
(FileType.Texture, ObjectType.Accessory) => GamePaths.Accessory.Tex.Regex().Match(path),
|
||||
(FileType.Texture, ObjectType.Character) => TestCharacterTextures(path),
|
||||
(FileType.Texture, ObjectType.Icon) => GamePaths.Icon.Regex().Match(path),
|
||||
(FileType.Texture, ObjectType.Map) => GamePaths.Map.Regex().Match(path),
|
||||
_ => Match.Empty,
|
||||
};
|
||||
|
||||
return (fileType, objectType, match.Success ? match : null);
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleEquipment(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
var setId = ushort.Parse(groups["id"].Value);
|
||||
if (fileType == FileType.Imc)
|
||||
return GameObjectInfo.Equipment(fileType, setId);
|
||||
|
||||
var gr = Names.GenderRaceFromCode(groups["race"].Value);
|
||||
var slot = Names.SuffixToEquipSlot[groups["slot"].Value];
|
||||
if (fileType == FileType.Model)
|
||||
return GameObjectInfo.Equipment(fileType, setId, gr, slot);
|
||||
|
||||
var variant = byte.Parse(groups["variant"].Value);
|
||||
return GameObjectInfo.Equipment(fileType, setId, gr, slot, variant);
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleWeapon(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
var weaponId = ushort.Parse(groups["weapon"].Value);
|
||||
var setId = ushort.Parse(groups["id"].Value);
|
||||
if (fileType is FileType.Imc or FileType.Model)
|
||||
return GameObjectInfo.Weapon(fileType, setId, weaponId);
|
||||
|
||||
var variant = byte.Parse(groups["variant"].Value);
|
||||
return GameObjectInfo.Weapon(fileType, setId, weaponId, variant);
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleMonster(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
var monsterId = ushort.Parse(groups["monster"].Value);
|
||||
var bodyId = ushort.Parse(groups["id"].Value);
|
||||
if (fileType is FileType.Imc or FileType.Model)
|
||||
return GameObjectInfo.Monster(fileType, monsterId, bodyId);
|
||||
|
||||
var variant = byte.Parse(groups["variant"].Value);
|
||||
return GameObjectInfo.Monster(fileType, monsterId, bodyId, variant);
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleDemiHuman(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
var demiHumanId = ushort.Parse(groups["id"].Value);
|
||||
var equipId = ushort.Parse(groups["equip"].Value);
|
||||
if (fileType == FileType.Imc)
|
||||
return GameObjectInfo.DemiHuman(fileType, demiHumanId, equipId);
|
||||
|
||||
var slot = Names.SuffixToEquipSlot[groups["slot"].Value];
|
||||
if (fileType == FileType.Model)
|
||||
return GameObjectInfo.DemiHuman(fileType, demiHumanId, equipId, slot);
|
||||
|
||||
var variant = byte.Parse(groups["variant"].Value);
|
||||
return GameObjectInfo.DemiHuman(fileType, demiHumanId, equipId, slot, variant);
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleCustomization(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
if (groups["catchlight"].Success)
|
||||
return GameObjectInfo.Customization(fileType, CustomizationType.Iris);
|
||||
|
||||
if (groups["skin"].Success)
|
||||
return GameObjectInfo.Customization(fileType, CustomizationType.Skin);
|
||||
|
||||
var id = ushort.Parse(groups["id"].Value);
|
||||
if (groups["location"].Success)
|
||||
{
|
||||
var tmpType = groups["location"].Value == "face" ? CustomizationType.DecalFace
|
||||
: groups["location"].Value == "equip" ? CustomizationType.DecalEquip : CustomizationType.Unknown;
|
||||
return GameObjectInfo.Customization(fileType, tmpType, id);
|
||||
}
|
||||
|
||||
var gr = Names.GenderRaceFromCode(groups["race"].Value);
|
||||
var bodySlot = Names.StringToBodySlot[groups["type"].Value];
|
||||
var type = groups["slot"].Success
|
||||
? Names.SuffixToCustomizationType[groups["slot"].Value]
|
||||
: CustomizationType.Skin;
|
||||
if (fileType == FileType.Material)
|
||||
{
|
||||
var variant = groups["variant"].Success ? byte.Parse(groups["variant"].Value) : (byte)0;
|
||||
return GameObjectInfo.Customization(fileType, type, id, gr, bodySlot, variant);
|
||||
}
|
||||
|
||||
return GameObjectInfo.Customization(fileType, type, id, gr, bodySlot);
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleIcon(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
var hq = groups["hq"].Success;
|
||||
var hr = groups["hr"].Success;
|
||||
var id = uint.Parse(groups["id"].Value);
|
||||
if (!groups["lang"].Success)
|
||||
return GameObjectInfo.Icon(fileType, id, hq, hr);
|
||||
|
||||
var language = groups["lang"].Value switch
|
||||
{
|
||||
"en" => Dalamud.ClientLanguage.English,
|
||||
"ja" => Dalamud.ClientLanguage.Japanese,
|
||||
"de" => Dalamud.ClientLanguage.German,
|
||||
"fr" => Dalamud.ClientLanguage.French,
|
||||
_ => Dalamud.ClientLanguage.English,
|
||||
};
|
||||
return GameObjectInfo.Icon(fileType, id, hq, hr, language);
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleMap(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
var map = Encoding.ASCII.GetBytes(groups["id"].Value);
|
||||
var variant = byte.Parse(groups["variant"].Value);
|
||||
if (groups["suffix"].Success)
|
||||
{
|
||||
var suffix = Encoding.ASCII.GetBytes(groups["suffix"].Value)[0];
|
||||
return GameObjectInfo.Map(fileType, map[0], map[1], map[2], map[3], variant, suffix);
|
||||
}
|
||||
|
||||
return GameObjectInfo.Map(fileType, map[0], map[1], map[2], map[3], variant);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,408 +0,0 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
public static partial class GamePaths
|
||||
{
|
||||
[GeneratedRegex(@"c(?'racecode'\d{4})")]
|
||||
public static partial Regex RaceCodeParser();
|
||||
|
||||
public static GenderRace ParseRaceCode(string path)
|
||||
{
|
||||
var match = RaceCodeParser().Match(path);
|
||||
return match.Success
|
||||
? Names.GenderRaceFromCode(match.Groups["racecode"].Value)
|
||||
: GenderRace.Unknown;
|
||||
}
|
||||
|
||||
public static partial class Monster
|
||||
{
|
||||
public static partial class Imc
|
||||
{
|
||||
[GeneratedRegex(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/b\k'id'\.imc")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId monsterId, SetId bodyId)
|
||||
=> $"chara/monster/m{monsterId.Value:D4}/obj/body/b{bodyId.Value:D4}/b{bodyId.Value:D4}.imc";
|
||||
}
|
||||
|
||||
public static partial class Mdl
|
||||
{
|
||||
[GeneratedRegex(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/model/m\k'monster'b\k'id'\.mdl")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId monsterId, SetId bodyId)
|
||||
=> $"chara/monster/m{monsterId.Value:D4}/obj/body/b{bodyId.Value:D4}/model/m{monsterId.Value:D4}b{bodyId.Value:D4}.mdl";
|
||||
}
|
||||
|
||||
public static partial class Mtrl
|
||||
{
|
||||
[GeneratedRegex(
|
||||
@"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")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId monsterId, SetId bodyId, byte variant, string suffix)
|
||||
=> $"chara/monster/m{monsterId.Value:D4}/obj/body/b{bodyId.Value:D4}/material/v{variant:D4}/mt_m{monsterId.Value:D4}b{bodyId.Value:D4}_{suffix}.mtrl";
|
||||
}
|
||||
|
||||
public static partial class Tex
|
||||
{
|
||||
[GeneratedRegex(
|
||||
@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/texture/v(?'variant'\d{2})_m\k'monster'b\k'id'(_[a-z])?_[a-z]\.tex")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId monsterId, SetId bodyId, byte variant, char suffix1, char suffix2 = '\0')
|
||||
=> $"chara/monster/m{monsterId.Value:D4}/obj/body/b{bodyId.Value:D4}/texture/v{variant:D2}_m{monsterId.Value:D4}b{bodyId.Value:D4}{(suffix2 != '\0' ? $"_{suffix2}" : string.Empty)}_{suffix1}.tex";
|
||||
}
|
||||
|
||||
public static partial class Sklb
|
||||
{
|
||||
public static string Path(SetId monsterId)
|
||||
=> $"chara/monster/m{monsterId.Value:D4}/skeleton/base/b0001/skl_m{monsterId.Value:D4}b0001.sklb";
|
||||
}
|
||||
|
||||
public static partial class Skp
|
||||
{
|
||||
public static string Path(SetId monsterId)
|
||||
=> $"chara/monster/m{monsterId.Value:D4}/skeleton/base/b0001/skl_m{monsterId.Value:D4}b0001.skp";
|
||||
}
|
||||
|
||||
public static partial class Eid
|
||||
{
|
||||
public static string Path(SetId monsterId)
|
||||
=> $"chara/monster/m{monsterId.Value:D4}/skeleton/base/b0001/eid_m{monsterId.Value:D4}b0001.eid";
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Weapon
|
||||
{
|
||||
public static partial class Imc
|
||||
{
|
||||
[GeneratedRegex(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/b\k'weapon'\.imc")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId weaponId, SetId bodyId)
|
||||
=> $"chara/weapon/w{weaponId.Value:D4}/obj/body/b{bodyId.Value:D4}/b{bodyId.Value:D4}.imc";
|
||||
}
|
||||
|
||||
public static partial class Mdl
|
||||
{
|
||||
[GeneratedRegex(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/model/w\k'id'b\k'weapon'\.mdl")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId weaponId, SetId bodyId)
|
||||
=> $"chara/weapon/w{weaponId.Value:D4}/obj/body/b{bodyId.Value:D4}/model/w{weaponId.Value:D4}b{bodyId.Value:D4}.mdl";
|
||||
}
|
||||
|
||||
public static partial class Mtrl
|
||||
{
|
||||
[GeneratedRegex(
|
||||
@"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")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId weaponId, SetId bodyId, byte variant, string suffix)
|
||||
=> $"chara/weapon/w{weaponId.Value:D4}/obj/body/b{bodyId.Value:D4}/material/v{variant:D4}/mt_w{weaponId.Value:D4}b{bodyId.Value:D4}_{suffix}.mtrl";
|
||||
}
|
||||
|
||||
public static partial class Tex
|
||||
{
|
||||
[GeneratedRegex(
|
||||
@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/texture/v(?'variant'\d{2})_w\k'id'b\k'weapon'(_[a-z])?_[a-z]\.tex")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId weaponId, SetId bodyId, byte variant, char suffix1, char suffix2 = '\0')
|
||||
=> $"chara/weapon/w{weaponId.Value:D4}/obj/body/b{bodyId.Value:D4}/texture/v{variant:D2}_w{weaponId.Value:D4}b{bodyId.Value:D4}{(suffix2 != '\0' ? $"_{suffix2}" : string.Empty)}_{suffix1}.tex";
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class DemiHuman
|
||||
{
|
||||
public static partial class Imc
|
||||
{
|
||||
[GeneratedRegex(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/e\k'equip'\.imc")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId demiId, SetId equipId)
|
||||
=> $"chara/demihuman/d{demiId.Value:D4}/obj/equipment/e{equipId.Value:D4}/e{equipId.Value:D4}.imc";
|
||||
}
|
||||
|
||||
public static partial class Mdl
|
||||
{
|
||||
[GeneratedRegex(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/model/d\k'id'e\k'equip'_(?'slot'[a-z]{3})\.mdl")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId demiId, SetId equipId, EquipSlot slot)
|
||||
=> $"chara/demihuman/d{demiId.Value:D4}/obj/equipment/e{equipId.Value:D4}/model/d{demiId.Value:D4}e{equipId.Value:D4}_{slot.ToSuffix()}.mdl";
|
||||
}
|
||||
|
||||
public static partial class Mtrl
|
||||
{
|
||||
[GeneratedRegex(
|
||||
@"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")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId demiId, SetId equipId, EquipSlot slot, byte variant, string suffix)
|
||||
=> $"chara/demihuman/d{demiId.Value:D4}/obj/equipment/e{equipId.Value:D4}/material/v{variant:D4}/mt_d{demiId.Value:D4}e{equipId.Value:D4}_{slot.ToSuffix()}_{suffix}.mtrl";
|
||||
}
|
||||
|
||||
public static partial class Tex
|
||||
{
|
||||
[GeneratedRegex(
|
||||
@"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")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId demiId, SetId equipId, EquipSlot slot, byte variant, char suffix1, char suffix2 = '\0')
|
||||
=> $"chara/demihuman/d{demiId.Value:D4}/obj/equipment/e{equipId.Value:D4}/texture/v{variant:D2}_d{demiId.Value:D4}e{equipId.Value:D4}_{slot.ToSuffix()}{(suffix2 != '\0' ? $"_{suffix2}" : string.Empty)}_{suffix1}.tex";
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Equipment
|
||||
{
|
||||
public static partial class Imc
|
||||
{
|
||||
[GeneratedRegex(@"chara/equipment/e(?'id'\d{4})/e\k'id'\.imc")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId equipId)
|
||||
=> $"chara/equipment/e{equipId.Value:D4}/e{equipId.Value:D4}.imc";
|
||||
}
|
||||
|
||||
public static partial class Mdl
|
||||
{
|
||||
[GeneratedRegex(@"chara/equipment/e(?'id'\d{4})/model/c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})\.mdl")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId equipId, GenderRace raceCode, EquipSlot slot)
|
||||
=> $"chara/equipment/e{equipId.Value:D4}/model/c{raceCode.ToRaceCode()}e{equipId.Value:D4}_{slot.ToSuffix()}.mdl";
|
||||
}
|
||||
|
||||
public static partial class Mtrl
|
||||
{
|
||||
[GeneratedRegex(
|
||||
@"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")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId equipId, GenderRace raceCode, EquipSlot slot, byte variant, string suffix)
|
||||
=> $"{FolderPath(equipId, variant)}/mt_c{raceCode.ToRaceCode()}e{equipId.Value:D4}_{slot.ToSuffix()}_{suffix}.mtrl";
|
||||
|
||||
public static string FolderPath(SetId equipId, byte variant)
|
||||
=> $"chara/equipment/e{equipId.Value:D4}/material/v{variant:D4}";
|
||||
}
|
||||
|
||||
public static partial class Tex
|
||||
{
|
||||
[GeneratedRegex(
|
||||
@"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")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId equipId, GenderRace raceCode, EquipSlot slot, byte variant, char suffix1, char suffix2 = '\0')
|
||||
=> $"chara/equipment/e{equipId.Value:D4}/texture/v{variant:D2}_c{raceCode.ToRaceCode()}e{equipId.Value:D4}_{slot.ToSuffix()}{(suffix2 != '\0' ? $"_{suffix2}" : string.Empty)}_{suffix1}.tex";
|
||||
}
|
||||
|
||||
public static partial class Avfx
|
||||
{
|
||||
[GeneratedRegex(@"chara/equipment/e(?'id'\d{4})/vfx/eff/ve(?'variant'\d{4})\.avfx")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId equipId, byte effectId)
|
||||
=> $"chara/equipment/e{equipId.Value:D4}/vfx/eff/ve{effectId:D4}.avfx";
|
||||
}
|
||||
|
||||
public static partial class Decal
|
||||
{
|
||||
[GeneratedRegex(@"chara/common/texture/decal_equip/-decal_(?'decalId'\d{3})\.tex")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(byte decalId)
|
||||
=> $"chara/common/texture/decal_equip/-decal_{decalId:D3}.tex";
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Accessory
|
||||
{
|
||||
public static partial class Imc
|
||||
{
|
||||
[GeneratedRegex(@"chara/accessory/a(?'id'\d{4})/a\k'id'\.imc")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId accessoryId)
|
||||
=> $"chara/accessory/a{accessoryId.Value:D4}/a{accessoryId.Value:D4}.imc";
|
||||
}
|
||||
|
||||
public static partial class Mdl
|
||||
{
|
||||
[GeneratedRegex(@"chara/accessory/a(?'id'\d{4})/model/c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})\.mdl")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId accessoryId, GenderRace raceCode, EquipSlot slot)
|
||||
=> $"chara/accessory/a{accessoryId.Value:D4}/model/c{raceCode.ToRaceCode()}a{accessoryId.Value:D4}_{slot.ToSuffix()}.mdl";
|
||||
}
|
||||
|
||||
public static partial class Mtrl
|
||||
{
|
||||
[GeneratedRegex(
|
||||
@"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")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId accessoryId, GenderRace raceCode, EquipSlot slot, byte variant, string suffix)
|
||||
=> $"{FolderPath(accessoryId, variant)}/c{raceCode.ToRaceCode()}a{accessoryId.Value:D4}_{slot.ToSuffix()}_{suffix}.mtrl";
|
||||
|
||||
public static string FolderPath(SetId accessoryId, byte variant)
|
||||
=> $"chara/accessory/a{accessoryId.Value:D4}/material/v{variant:D4}";
|
||||
}
|
||||
|
||||
public static partial class Tex
|
||||
{
|
||||
[GeneratedRegex(
|
||||
@"chara/accessory/a(?'id'\d{4})/texture/v(?'variant'\d{2})_c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})(_[a-z])?_[a-z]\.tex")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId accessoryId, GenderRace raceCode, EquipSlot slot, byte variant, char suffix1, char suffix2 = '\0')
|
||||
=> $"chara/accessory/a{accessoryId.Value:D4}/texture/v{variant:D2}_c{raceCode.ToRaceCode()}a{accessoryId.Value:D4}_{slot.ToSuffix()}{(suffix2 != '\0' ? $"_{suffix2}" : string.Empty)}_{suffix1}.tex";
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Skeleton
|
||||
{
|
||||
public static partial class Phyb
|
||||
{
|
||||
public static string Path(GenderRace raceCode, string slot, SetId slotId)
|
||||
=> $"chara/human/c{raceCode.ToRaceCode()}/skeleton/{slot}/{slot[0]}{slotId.Value:D4}/phy_c{raceCode.ToRaceCode()}{slot[0]}{slotId.Value:D4}.phyb";
|
||||
}
|
||||
|
||||
public static partial class Sklb
|
||||
{
|
||||
public static string Path(GenderRace raceCode, string slot, SetId slotId)
|
||||
=> $"chara/human/c{raceCode.ToRaceCode()}/skeleton/{slot}/{slot[0]}{slotId.Value:D4}/skl_c{raceCode.ToRaceCode()}{slot[0]}{slotId.Value:D4}.sklb";
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Character
|
||||
{
|
||||
public static partial class Mdl
|
||||
{
|
||||
[GeneratedRegex(
|
||||
@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/model/c\k'race'\k'typeabr'\k'id'_(?'slot'[a-z]{3})\.mdl")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(GenderRace raceCode, BodySlot slot, SetId slotId, CustomizationType type)
|
||||
=> $"chara/human/c{raceCode.ToRaceCode()}/obj/{slot.ToSuffix()}/{slot.ToAbbreviation()}{slotId.Value:D4}/model/c{raceCode.ToRaceCode()}{slot.ToAbbreviation()}{slotId.Value:D4}_{type.ToSuffix()}.mdl";
|
||||
}
|
||||
|
||||
public static partial class Mtrl
|
||||
{
|
||||
[GeneratedRegex(
|
||||
@"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")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string FolderPath(GenderRace raceCode, BodySlot slot, SetId slotId, byte variant = byte.MaxValue)
|
||||
=> $"chara/human/c{raceCode.ToRaceCode()}/obj/{slot.ToSuffix()}/{slot.ToAbbreviation()}{slotId.Value:D4}/material{(variant != byte.MaxValue ? $"/v{variant:D4}" : string.Empty)}";
|
||||
|
||||
public static string HairPath(GenderRace raceCode, SetId slotId, string fileName, out GenderRace actualGr)
|
||||
{
|
||||
actualGr = MaterialHandling.GetGameGenderRace(raceCode, slotId);
|
||||
var folder = FolderPath(actualGr, BodySlot.Hair, slotId, 1);
|
||||
return actualGr == raceCode
|
||||
? $"{folder}{fileName}"
|
||||
: $"{folder}/mt_c{actualGr.ToRaceCode()}{fileName[9..]}";
|
||||
}
|
||||
|
||||
public static string TailPath(GenderRace raceCode, SetId slotId, string fileName, byte variant, out SetId actualSlotId)
|
||||
{
|
||||
switch (raceCode)
|
||||
{
|
||||
case GenderRace.HrothgarMale:
|
||||
case GenderRace.HrothgarFemale:
|
||||
case GenderRace.HrothgarMaleNpc:
|
||||
case GenderRace.HrothgarFemaleNpc:
|
||||
var folder = FolderPath(raceCode, BodySlot.Tail, 1, variant == byte.MaxValue ? (byte)1 : variant);
|
||||
actualSlotId = 1;
|
||||
return $"{folder}{fileName}";
|
||||
default:
|
||||
actualSlotId = slotId;
|
||||
return $"{FolderPath(raceCode, BodySlot.Tail, slotId, variant)}{fileName}";
|
||||
}
|
||||
}
|
||||
|
||||
public static string Path(GenderRace raceCode, BodySlot slot, SetId slotId, string fileName,
|
||||
out GenderRace actualGr, out SetId actualSlotId, byte variant = byte.MaxValue)
|
||||
{
|
||||
switch (slot)
|
||||
{
|
||||
case BodySlot.Hair:
|
||||
actualSlotId = slotId;
|
||||
return HairPath(raceCode, slotId, fileName, out actualGr);
|
||||
case BodySlot.Tail:
|
||||
actualGr = raceCode;
|
||||
return TailPath(raceCode, slotId, fileName, variant, out actualSlotId);
|
||||
default:
|
||||
actualSlotId = slotId;
|
||||
actualGr = raceCode;
|
||||
return $"{FolderPath(raceCode, slot, slotId, variant)}{fileName}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Tex
|
||||
{
|
||||
[GeneratedRegex(
|
||||
@"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")]
|
||||
public static partial Regex Regex();
|
||||
|
||||
public static string Path(GenderRace raceCode, BodySlot slot, SetId slotId, char suffix1, bool minus = false,
|
||||
CustomizationType type = CustomizationType.Unknown, byte variant = byte.MaxValue, char suffix2 = '\0')
|
||||
=> $"chara/human/c{raceCode.ToRaceCode()}/obj/{slot.ToSuffix()}/{slot.ToAbbreviation()}{slotId.Value:D4}/texture/"
|
||||
+ (minus ? "--" : string.Empty)
|
||||
+ (variant != byte.MaxValue ? $"v{variant:D2}_" : string.Empty)
|
||||
+ $"c{raceCode.ToRaceCode()}{slot.ToAbbreviation()}{slotId.Value:D4}{(type != CustomizationType.Unknown ? $"_{type.ToSuffix()}" : string.Empty)}{(suffix2 != '\0' ? $"_{suffix2}" : string.Empty)}_{suffix1}.tex";
|
||||
|
||||
|
||||
[GeneratedRegex(@"chara/common/texture/(?'catchlight'catchlight)(.*)\.tex")]
|
||||
public static partial Regex CatchlightRegex();
|
||||
|
||||
[GeneratedRegex(@"chara/common/texture/skin(?'skin'.*)\.tex")]
|
||||
public static partial Regex SkinRegex();
|
||||
|
||||
[GeneratedRegex(@"chara/common/texture/decal_(?'location'[a-z]+)/[-_]?decal_(?'id'\d+).tex")]
|
||||
public static partial Regex DecalRegex();
|
||||
|
||||
[GeneratedRegex(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/texture")]
|
||||
public static partial Regex FolderRegex();
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Icon
|
||||
{
|
||||
[GeneratedRegex(@"ui/icon/(?'group'\d*)(/(?'lang'[a-z]{2}))?(/(?'hq'hq))?/(?'id'\d*)(?'hr'_hr1)?\.tex")]
|
||||
public static partial Regex Regex();
|
||||
}
|
||||
|
||||
public static partial class Map
|
||||
{
|
||||
[GeneratedRegex(@"ui/map/(?'id'[a-z0-9]{4})/(?'variant'\d{2})/\k'id'\k'variant'(?'suffix'[a-z])?(_[a-z])?\.tex")]
|
||||
public static partial Regex Regex();
|
||||
}
|
||||
|
||||
public static partial class Font
|
||||
{
|
||||
[GeneratedRegex(@"common/font/(?'fontname'.*)_(?'id'\d\d)(_lobby)?\.fdt")]
|
||||
public static partial Regex Regex();
|
||||
}
|
||||
|
||||
public static partial class Vfx
|
||||
{
|
||||
[GeneratedRegex(@"chara[\/]action[\/](?'key'[^\s]+?)\.tmb", RegexOptions.IgnoreCase)]
|
||||
public static partial Regex Tmb();
|
||||
|
||||
[GeneratedRegex(@"chara[\/]human[\/]c0101[\/]animation[\/]a0001[\/][^\s]+?[\/](?'key'[^\s]+?)\.pap", RegexOptions.IgnoreCase)]
|
||||
public static partial Regex Pap();
|
||||
}
|
||||
|
||||
public static partial class Shader
|
||||
{
|
||||
public static string ShpkPath(string name)
|
||||
=> $"shader/sm5/shpk/{name}";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
using System.Collections;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
public sealed class HumanModelList : DataSharer
|
||||
{
|
||||
public const string Tag = "HumanModels";
|
||||
public const int CurrentVersion = 2;
|
||||
|
||||
private readonly BitArray _humanModels;
|
||||
|
||||
public HumanModelList(DalamudPluginInterface pluginInterface, DataManager gameData)
|
||||
: base(pluginInterface, ClientLanguage.English, CurrentVersion)
|
||||
{
|
||||
_humanModels = TryCatchData(Tag, () => GetValidHumanModels(gameData));
|
||||
}
|
||||
|
||||
public bool IsHuman(uint modelId)
|
||||
=> modelId < _humanModels.Count && _humanModels[(int)modelId];
|
||||
|
||||
public int Count
|
||||
=> _humanModels.Count;
|
||||
|
||||
protected override void DisposeInternal()
|
||||
{
|
||||
DisposeTag(Tag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Go through all ModelChara rows and return a bitfield of those that resolve to human models.
|
||||
/// </summary>
|
||||
private static BitArray GetValidHumanModels(DataManager gameData)
|
||||
{
|
||||
var sheet = gameData.GetExcelSheet<ModelChara>()!;
|
||||
var ret = new BitArray((int)sheet.RowCount, false);
|
||||
foreach (var (_, idx) in sheet.Select((m, i) => (m, i)).Where(p => p.m.Type == (byte)CharacterBase.ModelType.Human))
|
||||
ret[idx] = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
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;
|
||||
using PseudoEquipItem = System.ValueTuple<string, ulong, ushort, ushort, ushort, byte, byte>;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
public sealed class ItemData : DataSharer, IReadOnlyDictionary<FullEquipType, IReadOnlyList<EquipItem>>
|
||||
{
|
||||
private readonly IReadOnlyDictionary<uint, PseudoEquipItem> _mainItems;
|
||||
private readonly IReadOnlyDictionary<uint, PseudoEquipItem> _offItems;
|
||||
private readonly IReadOnlyDictionary<uint, PseudoEquipItem> _gauntlets;
|
||||
private readonly IReadOnlyList<IReadOnlyList<PseudoEquipItem>> _byType;
|
||||
|
||||
private static IReadOnlyList<IReadOnlyList<PseudoEquipItem>> CreateItems(DataManager dataManager, ClientLanguage language)
|
||||
{
|
||||
var tmp = Enum.GetValues<FullEquipType>().Select(_ => new List<EquipItem>(1024)).ToArray();
|
||||
|
||||
var itemSheet = dataManager.GetExcelSheet<Item>(language)!;
|
||||
foreach (var item in itemSheet.Where(i => i.Name.RawData.Length > 1))
|
||||
{
|
||||
var type = item.ToEquipType();
|
||||
if (type.IsWeapon() || type.IsTool())
|
||||
{
|
||||
var mh = EquipItem.FromMainhand(item);
|
||||
if (item.ModelMain != 0)
|
||||
tmp[(int)type].Add(mh);
|
||||
if (item.ModelSub != 0)
|
||||
{
|
||||
if (type is FullEquipType.Fists && item.ModelSub < 0x100000000)
|
||||
{
|
||||
tmp[(int)FullEquipType.Hands].Add(new EquipItem(mh.Name + $" (Gauntlets)", mh.Id, mh.IconId, (SetId)item.ModelSub, 0,
|
||||
(byte)(item.ModelSub >> 16), FullEquipType.Hands));
|
||||
tmp[(int)FullEquipType.FistsOff].Add(new EquipItem(mh.Name + FullEquipType.FistsOff.OffhandTypeSuffix(), mh.Id,
|
||||
mh.IconId, (SetId)(mh.ModelId.Value + 50), mh.WeaponType, mh.Variant, FullEquipType.FistsOff));
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp[(int)type.ValidOffhand()].Add(EquipItem.FromOffhand(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type != FullEquipType.Unknown)
|
||||
{
|
||||
tmp[(int)type].Add(EquipItem.FromArmor(item));
|
||||
}
|
||||
}
|
||||
|
||||
var ret = new IReadOnlyList<PseudoEquipItem>[tmp.Length];
|
||||
ret[0] = Array.Empty<PseudoEquipItem>();
|
||||
for (var i = 1; i < tmp.Length; ++i)
|
||||
ret[i] = tmp[i].OrderBy(item => item.Name).Select(s => (PseudoEquipItem)s).ToArray();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Tuple<IReadOnlyDictionary<uint, PseudoEquipItem>, IReadOnlyDictionary<uint, PseudoEquipItem>> CreateMainItems(
|
||||
IReadOnlyList<IReadOnlyList<PseudoEquipItem>> items)
|
||||
{
|
||||
var dict = new Dictionary<uint, PseudoEquipItem>(1024 * 4);
|
||||
foreach (var fistWeapon in items[(int)FullEquipType.Fists])
|
||||
dict.TryAdd((uint)fistWeapon.Item2, fistWeapon);
|
||||
|
||||
var gauntlets = items[(int)FullEquipType.Hands].Where(g => dict.ContainsKey((uint)g.Item2)).ToDictionary(g => (uint)g.Item2, g => g);
|
||||
gauntlets.TrimExcess();
|
||||
|
||||
foreach (var type in Enum.GetValues<FullEquipType>().Where(v => !FullEquipTypeExtensions.OffhandTypes.Contains(v)))
|
||||
{
|
||||
var list = items[(int)type];
|
||||
foreach (var item in list)
|
||||
dict.TryAdd((uint)item.Item2, item);
|
||||
}
|
||||
|
||||
dict.TrimExcess();
|
||||
return new Tuple<IReadOnlyDictionary<uint, (string, ulong, ushort, ushort, ushort, byte, byte)>,
|
||||
IReadOnlyDictionary<uint, (string, ulong, ushort, ushort, ushort, byte, byte)>>(dict, gauntlets);
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<uint, PseudoEquipItem> CreateOffItems(IReadOnlyList<IReadOnlyList<PseudoEquipItem>> items)
|
||||
{
|
||||
var dict = new Dictionary<uint, PseudoEquipItem>(128);
|
||||
foreach (var type in FullEquipTypeExtensions.OffhandTypes)
|
||||
{
|
||||
var list = items[(int)type];
|
||||
foreach (var item in list)
|
||||
dict.TryAdd((uint)item.Item2, item);
|
||||
}
|
||||
|
||||
dict.TrimExcess();
|
||||
return dict;
|
||||
}
|
||||
|
||||
public ItemData(DalamudPluginInterface pluginInterface, DataManager dataManager, ClientLanguage language)
|
||||
: base(pluginInterface, language, 4)
|
||||
{
|
||||
_byType = TryCatchData("ItemList", () => CreateItems(dataManager, language));
|
||||
(_mainItems, _gauntlets) = TryCatchData("ItemDictMain", () => CreateMainItems(_byType));
|
||||
_offItems = TryCatchData("ItemDictOff", () => CreateOffItems(_byType));
|
||||
}
|
||||
|
||||
protected override void DisposeInternal()
|
||||
{
|
||||
DisposeTag("ItemList");
|
||||
DisposeTag("ItemDictMain");
|
||||
DisposeTag("ItemDictOff");
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<FullEquipType, IReadOnlyList<EquipItem>>> GetEnumerator()
|
||||
{
|
||||
for (var i = 1; i < _byType.Count; ++i)
|
||||
yield return new KeyValuePair<FullEquipType, IReadOnlyList<EquipItem>>((FullEquipType)i, new EquipItemList(_byType[i]));
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public int Count
|
||||
=> _byType.Count - 1;
|
||||
|
||||
public bool ContainsKey(FullEquipType key)
|
||||
=> (int)key < _byType.Count && key != FullEquipType.Unknown;
|
||||
|
||||
public bool TryGetValue(FullEquipType key, out IReadOnlyList<EquipItem> value)
|
||||
{
|
||||
if (ContainsKey(key))
|
||||
{
|
||||
value = new EquipItemList(_byType[(int)key]);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = Array.Empty<EquipItem>();
|
||||
return false;
|
||||
}
|
||||
|
||||
public IReadOnlyList<EquipItem> this[FullEquipType key]
|
||||
=> TryGetValue(key, out var ret) ? ret : throw new IndexOutOfRangeException();
|
||||
|
||||
public IEnumerable<(uint, EquipItem)> AllItems(bool main)
|
||||
=> (main ? _mainItems : _offItems).Select(i => (i.Key, (EquipItem)i.Value));
|
||||
|
||||
public int TotalItemCount(bool main)
|
||||
=> main ? _mainItems.Count : _offItems.Count;
|
||||
|
||||
public bool TryGetValue(uint key, EquipSlot slot, out EquipItem value)
|
||||
{
|
||||
var dict = slot is EquipSlot.OffHand ? _offItems : _mainItems;
|
||||
if (slot is EquipSlot.Hands && _gauntlets.TryGetValue(key, out var v) || dict.TryGetValue(key, out v))
|
||||
{
|
||||
value = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerable<FullEquipType> Keys
|
||||
=> Enum.GetValues<FullEquipType>().Skip(1);
|
||||
|
||||
public IEnumerable<IReadOnlyList<EquipItem>> Values
|
||||
=> _byType.Skip(1).Select(l => (IReadOnlyList<EquipItem>)new EquipItemList(l));
|
||||
|
||||
private readonly struct EquipItemList : IReadOnlyList<EquipItem>
|
||||
{
|
||||
private readonly IReadOnlyList<PseudoEquipItem> _items;
|
||||
|
||||
public EquipItemList(IReadOnlyList<PseudoEquipItem> items)
|
||||
=> _items = items;
|
||||
|
||||
public IEnumerator<EquipItem> GetEnumerator()
|
||||
=> _items.Select(i => (EquipItem)i).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public int Count
|
||||
=> _items.Count;
|
||||
|
||||
public EquipItem this[int index]
|
||||
=> _items[index];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
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();
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
public static class MaterialHandling
|
||||
{
|
||||
public static GenderRace GetGameGenderRace(GenderRace actualGr, SetId hairId)
|
||||
{
|
||||
// Hrothgar do not share hairstyles.
|
||||
if (actualGr is GenderRace.HrothgarFemale or GenderRace.HrothgarMale)
|
||||
return actualGr;
|
||||
|
||||
// Some hairstyles are miqo'te specific but otherwise shared.
|
||||
if (hairId.Value is >= 101 and <= 115)
|
||||
{
|
||||
if (actualGr is GenderRace.MiqoteFemale or GenderRace.MiqoteMale)
|
||||
return actualGr;
|
||||
|
||||
return actualGr.Split().Item1 == Gender.Female ? GenderRace.MidlanderFemale : GenderRace.MidlanderMale;
|
||||
}
|
||||
|
||||
// All hairstyles above 116 are shared except for Hrothgar
|
||||
if (hairId.Value is >= 116 and <= 200)
|
||||
return actualGr.Split().Item1 == Gender.Female ? GenderRace.MidlanderFemale : GenderRace.MidlanderMale;
|
||||
|
||||
return actualGr;
|
||||
}
|
||||
|
||||
public static bool IsSpecialCase(GenderRace gr, SetId hairId)
|
||||
=> gr is GenderRace.MidlanderMale or GenderRace.MidlanderFemale && hairId.Value is >= 101 and <= 200;
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
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)!;
|
||||
}
|
||||
|
|
@ -1,331 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
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 const int IdentificationVersion = 3;
|
||||
|
||||
public IGamePathParser GamePathParser { get; } = new GamePathParser();
|
||||
public readonly IReadOnlyList<IReadOnlyList<uint>> BnpcNames;
|
||||
public readonly IReadOnlyList<IReadOnlyList<(string Name, ObjectKind Kind, uint Id)>> 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, ItemData itemData, ClientLanguage language)
|
||||
: base(pluginInterface, language, IdentificationVersion)
|
||||
{
|
||||
_actorData = new ActorManager.ActorManagerData(pluginInterface, dataManager, language);
|
||||
_equipment = new EquipmentIdentificationList(pluginInterface, language, itemData);
|
||||
_weapons = new WeaponIdentificationList(pluginInterface, language, itemData);
|
||||
Actions = TryCatchData("Actions", () => CreateActionList(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)
|
||||
{
|
||||
if (path.EndsWith(".pap", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".tmb", StringComparison.OrdinalIgnoreCase))
|
||||
if (IdentifyVfx(set, path))
|
||||
return;
|
||||
|
||||
var info = GamePathParser.GetFileInfo(path);
|
||||
IdentifyParsed(set, info);
|
||||
}
|
||||
|
||||
public Dictionary<string, object?> Identify(string path)
|
||||
{
|
||||
Dictionary<string, object?> ret = new();
|
||||
Identify(ret, path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public IEnumerable<EquipItem> Identify(SetId setId, WeaponType weaponType, ushort variant, EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.MainHand => _weapons.Between(setId, weaponType, (byte)variant),
|
||||
EquipSlot.OffHand => _weapons.Between(setId, weaponType, (byte)variant),
|
||||
_ => _equipment.Between(setId, slot, (byte)variant),
|
||||
};
|
||||
|
||||
public IReadOnlyList<uint> GetBnpcNames(uint bNpcId)
|
||||
=> bNpcId >= BnpcNames.Count ? Array.Empty<uint>() : BnpcNames[(int)bNpcId];
|
||||
|
||||
public IReadOnlyList<(string Name, ObjectKind Kind, uint Id)> ModelCharaNames(uint modelId)
|
||||
=> modelId >= ModelCharaToObjects.Count ? Array.Empty<(string Name, ObjectKind Kind, uint Id)>() : ModelCharaToObjects[(int)modelId];
|
||||
|
||||
public int NumModelChara
|
||||
=> ModelCharaToObjects.Count;
|
||||
|
||||
protected override void DisposeInternal()
|
||||
{
|
||||
_actorData.Dispose();
|
||||
_weapons.Dispose(PluginInterface, Language);
|
||||
_equipment.Dispose(PluginInterface, Language);
|
||||
DisposeTag("Actions");
|
||||
DisposeTag("Models");
|
||||
|
||||
_modelIdentifierToModelChara.Dispose(PluginInterface, Language);
|
||||
DisposeTag("BNpcNames");
|
||||
DisposeTag("ModelObjects");
|
||||
}
|
||||
|
||||
private IReadOnlyDictionary<string, IReadOnlyList<Action>> CreateActionList(DataManager gameData)
|
||||
{
|
||||
var sheet = gameData.GetExcelSheet<Action>(Language)!;
|
||||
var storage = new ConcurrentDictionary<string, ConcurrentBag<Action>>();
|
||||
|
||||
void AddAction(string? key, Action action)
|
||||
{
|
||||
if (key.IsNullOrEmpty())
|
||||
return;
|
||||
|
||||
key = key.ToLowerInvariant();
|
||||
if (storage.TryGetValue(key, out var actions))
|
||||
actions.Add(action);
|
||||
else
|
||||
storage[key] = new ConcurrentBag<Action> { action };
|
||||
}
|
||||
|
||||
var options = new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = Environment.ProcessorCount,
|
||||
};
|
||||
|
||||
Parallel.ForEach(sheet.Where(a => !a.Name.RawData.IsEmpty), options, action =>
|
||||
{
|
||||
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();
|
||||
AddAction(startKey, action);
|
||||
AddAction(endKey, action);
|
||||
AddAction(hitKey, action);
|
||||
});
|
||||
|
||||
return storage.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<Action>)kvp.Value.ToArray());
|
||||
}
|
||||
|
||||
private void FindEquipment(IDictionary<string, object?> set, GameObjectInfo info)
|
||||
{
|
||||
var items = _equipment.Between(info.PrimaryId, info.EquipSlot, info.Variant);
|
||||
foreach (var item in items)
|
||||
set[item.Name] = item;
|
||||
}
|
||||
|
||||
private void FindWeapon(IDictionary<string, object?> set, GameObjectInfo info)
|
||||
{
|
||||
var items = _weapons.Between(info.PrimaryId, info.SecondaryId, info.Variant);
|
||||
foreach (var item in items)
|
||||
set[item.Name] = item;
|
||||
}
|
||||
|
||||
private void FindModel(IDictionary<string, object?> set, GameObjectInfo info)
|
||||
{
|
||||
var type = info.ObjectType.ToModelType();
|
||||
if (type is 0 or CharacterBase.ModelType.Weapon)
|
||||
return;
|
||||
|
||||
var models = _modelIdentifierToModelChara.Between(type, info.PrimaryId, (byte)info.SecondaryId, info.Variant);
|
||||
foreach (var model in models.Where(m => m.RowId != 0 && m.RowId < ModelCharaToObjects.Count))
|
||||
{
|
||||
var objectList = ModelCharaToObjects[(int)model.RowId];
|
||||
foreach (var (name, kind, _) in objectList)
|
||||
set[$"{name} ({kind.ToName()})"] = model;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddCounterString(IDictionary<string, object?> set, string data)
|
||||
{
|
||||
if (set.TryGetValue(data, out var obj) && obj is int counter)
|
||||
set[data] = counter + 1;
|
||||
else
|
||||
set[data] = 1;
|
||||
}
|
||||
|
||||
private void IdentifyParsed(IDictionary<string, object?> set, GameObjectInfo info)
|
||||
{
|
||||
switch (info.FileType)
|
||||
{
|
||||
case FileType.Sound:
|
||||
AddCounterString(set, FileType.Sound.ToString());
|
||||
return;
|
||||
case FileType.Animation:
|
||||
case FileType.Pap:
|
||||
AddCounterString(set, FileType.Animation.ToString());
|
||||
return;
|
||||
case FileType.Shader:
|
||||
AddCounterString(set, FileType.Shader.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
switch (info.ObjectType)
|
||||
{
|
||||
case ObjectType.LoadingScreen:
|
||||
case ObjectType.Map:
|
||||
case ObjectType.Interface:
|
||||
case ObjectType.Vfx:
|
||||
case ObjectType.World:
|
||||
case ObjectType.Housing:
|
||||
case ObjectType.Font:
|
||||
AddCounterString(set, info.ObjectType.ToString());
|
||||
break;
|
||||
case ObjectType.DemiHuman:
|
||||
FindModel(set, info);
|
||||
break;
|
||||
case ObjectType.Monster:
|
||||
FindModel(set, info);
|
||||
break;
|
||||
case ObjectType.Icon:
|
||||
set[$"Icon: {info.IconId}"] = null;
|
||||
break;
|
||||
case ObjectType.Accessory:
|
||||
case ObjectType.Equipment:
|
||||
FindEquipment(set, info);
|
||||
break;
|
||||
case ObjectType.Weapon:
|
||||
FindWeapon(set, info);
|
||||
break;
|
||||
case ObjectType.Character:
|
||||
var (gender, race) = info.GenderRace.Split();
|
||||
var raceString = race != ModelRace.Unknown ? race.ToName() + " " : "";
|
||||
var genderString = gender != Gender.Unknown ? gender.ToName() + " " : "Player ";
|
||||
switch (info.CustomizationType)
|
||||
{
|
||||
case CustomizationType.Skin:
|
||||
set[$"Customization: {raceString}{genderString}Skin Textures"] = null;
|
||||
break;
|
||||
case CustomizationType.DecalFace:
|
||||
set[$"Customization: Face Decal {info.PrimaryId}"] = null;
|
||||
break;
|
||||
case CustomizationType.Iris when race == ModelRace.Unknown:
|
||||
set[$"Customization: All Eyes (Catchlight)"] = null;
|
||||
break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IdentifyVfx(IDictionary<string, object?> set, string path)
|
||||
{
|
||||
var key = GamePathParser.VfxToKey(path);
|
||||
if (key.Length == 0 || !Actions.TryGetValue(key, out var actions) || actions.Count == 0)
|
||||
return false;
|
||||
|
||||
foreach (var action in actions)
|
||||
set[$"Action: {action.Name}"] = action;
|
||||
return true;
|
||||
}
|
||||
|
||||
private IReadOnlyList<IReadOnlyList<(string Name, ObjectKind Kind, uint Id)>> CreateModelObjects(ActorManager.ActorManagerData actors,
|
||||
DataManager gameData, ClientLanguage language)
|
||||
{
|
||||
var modelSheet = gameData.GetExcelSheet<ModelChara>(language)!;
|
||||
var ret = new List<ConcurrentBag<(string Name, ObjectKind Kind, uint Id)>>((int)modelSheet.RowCount);
|
||||
|
||||
for (var i = -1; i < modelSheet.Last().RowId; ++i)
|
||||
ret.Add(new ConcurrentBag<(string Name, ObjectKind Kind, uint Id)>());
|
||||
|
||||
void AddChara(int modelChara, ObjectKind kind, uint dataId, uint displayId)
|
||||
{
|
||||
if (modelChara >= ret.Count)
|
||||
return;
|
||||
|
||||
if (actors.TryGetName(kind, dataId, out var name))
|
||||
ret[modelChara].Add((name, kind, displayId));
|
||||
}
|
||||
|
||||
var oTask = Task.Run(() =>
|
||||
{
|
||||
foreach (var ornament in gameData.GetExcelSheet<Ornament>(language)!)
|
||||
AddChara(ornament.Model, ObjectKind.Ornament, ornament.RowId, ornament.RowId);
|
||||
});
|
||||
|
||||
var mTask = Task.Run(() =>
|
||||
{
|
||||
foreach (var mount in gameData.GetExcelSheet<Mount>(language)!)
|
||||
AddChara((int)mount.ModelChara.Row, ObjectKind.MountType, mount.RowId, mount.RowId);
|
||||
});
|
||||
|
||||
var cTask = Task.Run(() =>
|
||||
{
|
||||
foreach (var companion in gameData.GetExcelSheet<Companion>(language)!)
|
||||
AddChara((int)companion.Model.Row, ObjectKind.Companion, companion.RowId, companion.RowId);
|
||||
});
|
||||
|
||||
var eTask = Task.Run(() =>
|
||||
{
|
||||
foreach (var eNpc in gameData.GetExcelSheet<ENpcBase>(language)!)
|
||||
AddChara((int)eNpc.ModelChara.Row, ObjectKind.EventNpc, eNpc.RowId, eNpc.RowId);
|
||||
});
|
||||
|
||||
var options = new ParallelOptions()
|
||||
{
|
||||
MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2),
|
||||
};
|
||||
|
||||
Parallel.ForEach(gameData.GetExcelSheet<BNpcBase>(language)!.Where(b => b.RowId < BnpcNames.Count), options, bNpc =>
|
||||
{
|
||||
foreach (var name in BnpcNames[(int)bNpc.RowId])
|
||||
AddChara((int)bNpc.ModelChara.Row, ObjectKind.BattleNpc, name, bNpc.RowId);
|
||||
});
|
||||
|
||||
Task.WaitAll(oTask, mTask, cTask, eTask);
|
||||
|
||||
return ret.Select(s => !s.IsEmpty
|
||||
? s.ToArray()
|
||||
: Array.Empty<(string Name, ObjectKind Kind, uint Id)>()).ToArray();
|
||||
}
|
||||
|
||||
public static unsafe ulong KeyFromCharacterBase(CharacterBase* drawObject)
|
||||
{
|
||||
var type = (*(delegate* unmanaged<CharacterBase*, uint>**)drawObject)[Offsets.DrawObjectGetModelTypeVfunc](drawObject);
|
||||
var unk = (ulong)*((byte*)drawObject + Offsets.DrawObjectModelUnk1) << 8;
|
||||
return type switch
|
||||
{
|
||||
1 => type | unk,
|
||||
2 => type | unk | ((ulong)*(ushort*)((byte*)drawObject + Offsets.DrawObjectModelUnk3) << 16),
|
||||
3 => type
|
||||
| unk
|
||||
| ((ulong)*(ushort*)((byte*)drawObject + Offsets.DrawObjectModelUnk2) << 16)
|
||||
| ((ulong)**(ushort**)((byte*)drawObject + Offsets.DrawObjectModelUnk4) << 32)
|
||||
| ((ulong)**(ushort**)((byte*)drawObject + Offsets.DrawObjectModelUnk3) << 40),
|
||||
_ => 0u,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,433 +0,0 @@
|
|||
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.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Race = Penumbra.GameData.Enums.Race;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
/// <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;
|
||||
|
||||
public RestrictedGear(DalamudPluginInterface pi, ClientLanguage language, DataManager gameData)
|
||||
: base(pi, language, 2)
|
||||
{
|
||||
_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(rg, 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(IReadOnlySet<uint> rg, 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 (rg.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, 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, 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, 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, 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 <-> Blackbosom 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, 32393, 39302, false); // Edenmete Gown of Casting <- Gaia's Attire
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
public sealed class StainData : DataSharer, IReadOnlyDictionary<StainId, Stain>
|
||||
{
|
||||
public readonly IReadOnlyDictionary<byte, (string Name, uint Dye, bool Gloss)> Data;
|
||||
|
||||
public StainData(DalamudPluginInterface pluginInterface, DataManager dataManager, ClientLanguage language)
|
||||
: base(pluginInterface, language, 2)
|
||||
{
|
||||
Data = TryCatchData("Stains", () => CreateStainData(dataManager));
|
||||
}
|
||||
|
||||
protected override void DisposeInternal()
|
||||
=> DisposeTag("Stains");
|
||||
|
||||
private IReadOnlyDictionary<byte, (string Name, uint Dye, bool Gloss)> CreateStainData(DataManager dataManager)
|
||||
{
|
||||
var stainSheet = dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Stain>(Language)!;
|
||||
return stainSheet.Where(s => s.Color != 0 && s.Name.RawData.Length > 0)
|
||||
.ToDictionary(s => (byte)s.RowId, s =>
|
||||
{
|
||||
var stain = new Stain(s);
|
||||
return (stain.Name, stain.RgbaColor, stain.Gloss);
|
||||
});
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<StainId, Stain>> GetEnumerator()
|
||||
=> Data.Select(kvp
|
||||
=> new KeyValuePair<StainId, Stain>(new StainId(kvp.Key), new Stain(kvp.Value.Name, kvp.Value.Dye, kvp.Key, kvp.Value.Gloss)))
|
||||
.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public int Count
|
||||
=> Data.Count;
|
||||
|
||||
public bool ContainsKey(StainId key)
|
||||
=> Data.ContainsKey(key.Value);
|
||||
|
||||
public bool TryGetValue(StainId key, out Stain value)
|
||||
{
|
||||
if (!Data.TryGetValue(key.Value, out var data))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = new Stain(data.Name, data.Dye, key.Value, data.Gloss);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Stain this[StainId key]
|
||||
=> TryGetValue(key, out var data) ? data : throw new ArgumentOutOfRangeException(nameof(key));
|
||||
|
||||
public IEnumerable<StainId> Keys
|
||||
=> Data.Keys.Select(k => new StainId(k));
|
||||
|
||||
public IEnumerable<Stain> Values
|
||||
=> Data.Select(kvp => new Stain(kvp.Value.Name, kvp.Value.Dye, kvp.Key, kvp.Value.Gloss));
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Plugin;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using PseudoEquipItem = System.ValueTuple<string, ulong, ushort, ushort, ushort, byte, byte>;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
internal sealed class WeaponIdentificationList : KeyList<PseudoEquipItem>
|
||||
{
|
||||
private const string Tag = "WeaponIdentification";
|
||||
private const int Version = 2;
|
||||
|
||||
public WeaponIdentificationList(DalamudPluginInterface pi, ClientLanguage language, ItemData data)
|
||||
: base(pi, Tag, language, Version, CreateWeaponList(data))
|
||||
{ }
|
||||
|
||||
public IEnumerable<EquipItem> Between(SetId modelId)
|
||||
=> Between(ToKey(modelId, 0, 0), ToKey(modelId, 0xFFFF, 0xFF)).Select(e => (EquipItem)e);
|
||||
|
||||
public IEnumerable<EquipItem> Between(SetId modelId, WeaponType type, byte variant = 0)
|
||||
{
|
||||
if (type == 0)
|
||||
return Between(ToKey(modelId, 0, 0), ToKey(modelId, 0xFFFF, 0xFF)).Select(e => (EquipItem)e);
|
||||
if (variant == 0)
|
||||
return Between(ToKey(modelId, type, 0), ToKey(modelId, type, 0xFF)).Select(e => (EquipItem)e);
|
||||
|
||||
return Between(ToKey(modelId, type, variant), ToKey(modelId, type, variant)).Select(e => (EquipItem)e);
|
||||
}
|
||||
|
||||
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(EquipItem i)
|
||||
=> ToKey(i.ModelId, i.WeaponType, i.Variant);
|
||||
|
||||
protected override IEnumerable<ulong> ToKeys(PseudoEquipItem data)
|
||||
{
|
||||
yield return ToKey(data);
|
||||
}
|
||||
|
||||
protected override bool ValidKey(ulong key)
|
||||
=> key != 0;
|
||||
|
||||
protected override int ValueKeySelector(PseudoEquipItem data)
|
||||
=> (int)data.Item2;
|
||||
|
||||
private static IEnumerable<PseudoEquipItem> CreateWeaponList(ItemData data)
|
||||
=> data.Where(kvp => !kvp.Key.IsEquipment() && !kvp.Key.IsAccessory())
|
||||
.SelectMany(kvp => kvp.Value)
|
||||
.Select(i => (PseudoEquipItem)i);
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Penumbra.GameData.Enums;
|
||||
|
||||
public enum BodySlot : byte
|
||||
{
|
||||
Unknown,
|
||||
Hair,
|
||||
Face,
|
||||
Tail,
|
||||
Body,
|
||||
Zear,
|
||||
}
|
||||
|
||||
public static class BodySlotEnumExtension
|
||||
{
|
||||
public static string ToSuffix( this BodySlot value )
|
||||
=> value switch
|
||||
{
|
||||
BodySlot.Zear => "zear",
|
||||
BodySlot.Face => "face",
|
||||
BodySlot.Hair => "hair",
|
||||
BodySlot.Body => "body",
|
||||
BodySlot.Tail => "tail",
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
|
||||
public static char ToAbbreviation(this BodySlot value)
|
||||
=> value switch
|
||||
{
|
||||
BodySlot.Hair => 'h',
|
||||
BodySlot.Face => 'f',
|
||||
BodySlot.Tail => 't',
|
||||
BodySlot.Body => 'b',
|
||||
BodySlot.Zear => 'z',
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
|
||||
public static CustomizationType ToCustomizationType(this BodySlot value)
|
||||
=> value switch
|
||||
{
|
||||
BodySlot.Hair => CustomizationType.Hair,
|
||||
BodySlot.Face => CustomizationType.Face,
|
||||
BodySlot.Tail => CustomizationType.Tail,
|
||||
BodySlot.Body => CustomizationType.Body,
|
||||
BodySlot.Zear => CustomizationType.Zear,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(value), value, null)
|
||||
};
|
||||
}
|
||||
|
||||
public static partial class Names
|
||||
{
|
||||
public static readonly Dictionary< string, BodySlot > StringToBodySlot = new()
|
||||
{
|
||||
{ BodySlot.Zear.ToSuffix(), BodySlot.Zear },
|
||||
{ BodySlot.Face.ToSuffix(), BodySlot.Face },
|
||||
{ BodySlot.Hair.ToSuffix(), BodySlot.Hair },
|
||||
{ BodySlot.Body.ToSuffix(), BodySlot.Body },
|
||||
{ BodySlot.Tail.ToSuffix(), BodySlot.Tail },
|
||||
};
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
using System;
|
||||
using Dalamud.Data;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.Api.Enums;
|
||||
using Action = Lumina.Excel.GeneratedSheets.Action;
|
||||
|
||||
namespace Penumbra.GameData.Enums;
|
||||
|
||||
public static class ChangedItemExtensions
|
||||
{
|
||||
public static (ChangedItemType, uint) ChangedItemToTypeAndId(object? item)
|
||||
{
|
||||
return item switch
|
||||
{
|
||||
null => (ChangedItemType.None, 0),
|
||||
Item i => (ChangedItemType.Item, i.RowId),
|
||||
Action a => (ChangedItemType.Action, a.RowId),
|
||||
_ => (ChangedItemType.Customization, 0),
|
||||
};
|
||||
}
|
||||
|
||||
public static object? GetObject(this ChangedItemType type, DataManager manager, uint id)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ChangedItemType.None => null,
|
||||
ChangedItemType.Item => manager.GetExcelSheet<Item>()?.GetRow(id),
|
||||
ChangedItemType.Action => manager.GetExcelSheet<Action>()?.GetRow(id),
|
||||
ChangedItemType.Customization => null,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Penumbra.GameData.Enums
|
||||
{
|
||||
public enum CustomizationType : byte
|
||||
{
|
||||
Unknown,
|
||||
Body,
|
||||
Tail,
|
||||
Face,
|
||||
Iris,
|
||||
Accessory,
|
||||
Hair,
|
||||
Zear,
|
||||
DecalFace,
|
||||
DecalEquip,
|
||||
Skin,
|
||||
Etc,
|
||||
}
|
||||
|
||||
public static class CustomizationTypeEnumExtension
|
||||
{
|
||||
public static string ToSuffix( this CustomizationType value )
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
CustomizationType.Body => "top",
|
||||
CustomizationType.Face => "fac",
|
||||
CustomizationType.Iris => "iri",
|
||||
CustomizationType.Accessory => "acc",
|
||||
CustomizationType.Hair => "hir",
|
||||
CustomizationType.Tail => "til",
|
||||
CustomizationType.Zear => "zer",
|
||||
CustomizationType.Etc => "etc",
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Names
|
||||
{
|
||||
public static readonly Dictionary< string, CustomizationType > SuffixToCustomizationType = new()
|
||||
{
|
||||
{ CustomizationType.Body.ToSuffix(), CustomizationType.Body },
|
||||
{ CustomizationType.Face.ToSuffix(), CustomizationType.Face },
|
||||
{ CustomizationType.Iris.ToSuffix(), CustomizationType.Iris },
|
||||
{ CustomizationType.Accessory.ToSuffix(), CustomizationType.Accessory },
|
||||
{ CustomizationType.Hair.ToSuffix(), CustomizationType.Hair },
|
||||
{ CustomizationType.Tail.ToSuffix(), CustomizationType.Tail },
|
||||
{ CustomizationType.Zear.ToSuffix(), CustomizationType.Zear },
|
||||
{ CustomizationType.Etc.ToSuffix(), CustomizationType.Etc },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,232 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Penumbra.GameData.Enums;
|
||||
|
||||
public enum EquipSlot : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
MainHand = 1,
|
||||
OffHand = 2,
|
||||
Head = 3,
|
||||
Body = 4,
|
||||
Hands = 5,
|
||||
Belt = 6,
|
||||
Legs = 7,
|
||||
Feet = 8,
|
||||
Ears = 9,
|
||||
Neck = 10,
|
||||
Wrists = 11,
|
||||
RFinger = 12,
|
||||
BothHand = 13,
|
||||
LFinger = 14, // Not officially existing, means "weapon could be equipped in either hand" for the game.
|
||||
HeadBody = 15,
|
||||
BodyHandsLegsFeet = 16,
|
||||
SoulCrystal = 17,
|
||||
LegsFeet = 18,
|
||||
FullBody = 19,
|
||||
BodyHands = 20,
|
||||
BodyLegsFeet = 21,
|
||||
ChestHands = 22,
|
||||
Nothing = 23,
|
||||
All = 24, // Not officially existing
|
||||
}
|
||||
|
||||
public static class EquipSlotExtensions
|
||||
{
|
||||
public static EquipSlot ToEquipSlot(this uint value)
|
||||
=> value switch
|
||||
{
|
||||
0 => EquipSlot.Head,
|
||||
1 => EquipSlot.Body,
|
||||
2 => EquipSlot.Hands,
|
||||
3 => EquipSlot.Legs,
|
||||
4 => EquipSlot.Feet,
|
||||
5 => EquipSlot.Ears,
|
||||
6 => EquipSlot.Neck,
|
||||
7 => EquipSlot.Wrists,
|
||||
8 => EquipSlot.RFinger,
|
||||
9 => EquipSlot.LFinger,
|
||||
10 => EquipSlot.MainHand,
|
||||
11 => EquipSlot.OffHand,
|
||||
_ => EquipSlot.Unknown,
|
||||
};
|
||||
|
||||
public static uint ToIndex(this EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.Head => 0,
|
||||
EquipSlot.Body => 1,
|
||||
EquipSlot.Hands => 2,
|
||||
EquipSlot.Legs => 3,
|
||||
EquipSlot.Feet => 4,
|
||||
EquipSlot.Ears => 5,
|
||||
EquipSlot.Neck => 6,
|
||||
EquipSlot.Wrists => 7,
|
||||
EquipSlot.RFinger => 8,
|
||||
EquipSlot.LFinger => 9,
|
||||
EquipSlot.MainHand => 10,
|
||||
EquipSlot.OffHand => 11,
|
||||
_ => uint.MaxValue,
|
||||
};
|
||||
|
||||
public static string ToSuffix(this EquipSlot value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
EquipSlot.Head => "met",
|
||||
EquipSlot.Hands => "glv",
|
||||
EquipSlot.Legs => "dwn",
|
||||
EquipSlot.Feet => "sho",
|
||||
EquipSlot.Body => "top",
|
||||
EquipSlot.Ears => "ear",
|
||||
EquipSlot.Neck => "nek",
|
||||
EquipSlot.RFinger => "rir",
|
||||
EquipSlot.LFinger => "ril",
|
||||
EquipSlot.Wrists => "wrs",
|
||||
_ => "unk",
|
||||
};
|
||||
}
|
||||
|
||||
public static EquipSlot ToSlot(this EquipSlot value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
EquipSlot.MainHand => EquipSlot.MainHand,
|
||||
EquipSlot.OffHand => EquipSlot.OffHand,
|
||||
EquipSlot.Head => EquipSlot.Head,
|
||||
EquipSlot.Body => EquipSlot.Body,
|
||||
EquipSlot.Hands => EquipSlot.Hands,
|
||||
EquipSlot.Belt => EquipSlot.Belt,
|
||||
EquipSlot.Legs => EquipSlot.Legs,
|
||||
EquipSlot.Feet => EquipSlot.Feet,
|
||||
EquipSlot.Ears => EquipSlot.Ears,
|
||||
EquipSlot.Neck => EquipSlot.Neck,
|
||||
EquipSlot.Wrists => EquipSlot.Wrists,
|
||||
EquipSlot.RFinger => EquipSlot.RFinger,
|
||||
EquipSlot.BothHand => EquipSlot.MainHand,
|
||||
EquipSlot.LFinger => EquipSlot.RFinger,
|
||||
EquipSlot.HeadBody => EquipSlot.Body,
|
||||
EquipSlot.BodyHandsLegsFeet => EquipSlot.Body,
|
||||
EquipSlot.SoulCrystal => EquipSlot.SoulCrystal,
|
||||
EquipSlot.LegsFeet => EquipSlot.Legs,
|
||||
EquipSlot.FullBody => EquipSlot.Body,
|
||||
EquipSlot.BodyHands => EquipSlot.Body,
|
||||
EquipSlot.BodyLegsFeet => EquipSlot.Body,
|
||||
EquipSlot.ChestHands => EquipSlot.Body,
|
||||
_ => EquipSlot.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToName(this EquipSlot value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
EquipSlot.Head => "Head",
|
||||
EquipSlot.Hands => "Hands",
|
||||
EquipSlot.Legs => "Legs",
|
||||
EquipSlot.Feet => "Feet",
|
||||
EquipSlot.Body => "Body",
|
||||
EquipSlot.Ears => "Earrings",
|
||||
EquipSlot.Neck => "Necklace",
|
||||
EquipSlot.RFinger => "Right Ring",
|
||||
EquipSlot.LFinger => "Left Ring",
|
||||
EquipSlot.Wrists => "Bracelets",
|
||||
EquipSlot.MainHand => "Primary Weapon",
|
||||
EquipSlot.OffHand => "Secondary Weapon",
|
||||
EquipSlot.Belt => "Belt",
|
||||
EquipSlot.BothHand => "Primary Weapon",
|
||||
EquipSlot.HeadBody => "Head and Body",
|
||||
EquipSlot.BodyHandsLegsFeet => "Costume",
|
||||
EquipSlot.SoulCrystal => "Soul Crystal",
|
||||
EquipSlot.LegsFeet => "Bottom",
|
||||
EquipSlot.FullBody => "Costume",
|
||||
EquipSlot.BodyHands => "Top",
|
||||
EquipSlot.BodyLegsFeet => "Costume",
|
||||
EquipSlot.All => "Costume",
|
||||
_ => "Unknown",
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsEquipment(this EquipSlot value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
EquipSlot.Head => true,
|
||||
EquipSlot.Hands => true,
|
||||
EquipSlot.Legs => true,
|
||||
EquipSlot.Feet => true,
|
||||
EquipSlot.Body => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsAccessory(this EquipSlot value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
EquipSlot.Ears => true,
|
||||
EquipSlot.Neck => true,
|
||||
EquipSlot.RFinger => true,
|
||||
EquipSlot.LFinger => true,
|
||||
EquipSlot.Wrists => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
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 readonly EquipSlot[] WeaponSlots =
|
||||
{
|
||||
EquipSlot.MainHand,
|
||||
EquipSlot.OffHand,
|
||||
};
|
||||
|
||||
public static readonly EquipSlot[] FullSlots = WeaponSlots.Concat(EqdpSlots).ToArray();
|
||||
}
|
||||
|
||||
public static partial class Names
|
||||
{
|
||||
public static readonly Dictionary<string, EquipSlot> SuffixToEquipSlot = new()
|
||||
{
|
||||
{ EquipSlot.Head.ToSuffix(), EquipSlot.Head },
|
||||
{ EquipSlot.Hands.ToSuffix(), EquipSlot.Hands },
|
||||
{ EquipSlot.Legs.ToSuffix(), EquipSlot.Legs },
|
||||
{ EquipSlot.Feet.ToSuffix(), EquipSlot.Feet },
|
||||
{ EquipSlot.Body.ToSuffix(), EquipSlot.Body },
|
||||
{ EquipSlot.Ears.ToSuffix(), EquipSlot.Ears },
|
||||
{ EquipSlot.Neck.ToSuffix(), EquipSlot.Neck },
|
||||
{ EquipSlot.RFinger.ToSuffix(), EquipSlot.RFinger },
|
||||
{ EquipSlot.LFinger.ToSuffix(), EquipSlot.LFinger },
|
||||
{ EquipSlot.Wrists.ToSuffix(), EquipSlot.Wrists },
|
||||
};
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Penumbra.GameData.Enums;
|
||||
|
||||
public enum FileType : byte
|
||||
{
|
||||
Unknown,
|
||||
Sound,
|
||||
Imc,
|
||||
Vfx,
|
||||
Animation,
|
||||
Pap,
|
||||
MetaInfo,
|
||||
Material,
|
||||
Texture,
|
||||
Model,
|
||||
Shader,
|
||||
Font,
|
||||
Environment,
|
||||
}
|
||||
|
||||
public static partial class Names
|
||||
{
|
||||
public static readonly Dictionary< string, FileType > ExtensionToFileType = new()
|
||||
{
|
||||
{ ".mdl", FileType.Model },
|
||||
{ ".tex", FileType.Texture },
|
||||
{ ".mtrl", FileType.Material },
|
||||
{ ".atex", FileType.Animation },
|
||||
{ ".avfx", FileType.Vfx },
|
||||
{ ".scd", FileType.Sound },
|
||||
{ ".imc", FileType.Imc },
|
||||
{ ".pap", FileType.Pap },
|
||||
{ ".eqp", FileType.MetaInfo },
|
||||
{ ".eqdp", FileType.MetaInfo },
|
||||
{ ".est", FileType.MetaInfo },
|
||||
{ ".exd", FileType.MetaInfo },
|
||||
{ ".exh", FileType.MetaInfo },
|
||||
{ ".shpk", FileType.Shader },
|
||||
{ ".shcd", FileType.Shader },
|
||||
{ ".fdt", FileType.Font },
|
||||
{ ".envb", FileType.Environment },
|
||||
};
|
||||
}
|
||||
|
|
@ -1,450 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Penumbra.GameData.Enums;
|
||||
|
||||
public enum FullEquipType : byte
|
||||
{
|
||||
Unknown,
|
||||
|
||||
Head,
|
||||
Body,
|
||||
Hands,
|
||||
Legs,
|
||||
Feet,
|
||||
|
||||
Ears,
|
||||
Neck,
|
||||
Wrists,
|
||||
Finger,
|
||||
|
||||
Fists, // PGL, MNK
|
||||
FistsOff,
|
||||
Sword, // GLA, PLD Main
|
||||
Axe, // MRD, WAR
|
||||
Bow, // ARC, BRD
|
||||
BowOff,
|
||||
Lance, // LNC, DRG,
|
||||
Staff, // THM, BLM, CNJ, WHM
|
||||
Wand, // THM, BLM, CNJ, WHM Main
|
||||
Book, // ACN, SMN, SCH
|
||||
Daggers, // ROG, NIN
|
||||
DaggersOff,
|
||||
Broadsword, // DRK,
|
||||
Gun, // MCH,
|
||||
GunOff,
|
||||
Orrery, // AST,
|
||||
OrreryOff,
|
||||
Katana, // SAM
|
||||
KatanaOff,
|
||||
Rapier, // RDM
|
||||
RapierOff,
|
||||
Cane, // BLU
|
||||
Gunblade, // GNB,
|
||||
Glaives, // DNC,
|
||||
GlaivesOff,
|
||||
Scythe, // RPR,
|
||||
Nouliths, // SGE
|
||||
Shield, // GLA, PLD, THM, BLM, CNJ, WHM Off
|
||||
|
||||
Saw, // CRP
|
||||
CrossPeinHammer, // BSM
|
||||
RaisingHammer, // ARM
|
||||
LapidaryHammer, // GSM
|
||||
Knife, // LTW
|
||||
Needle, // WVR
|
||||
Alembic, // ALC
|
||||
Frypan, // CUL
|
||||
Pickaxe, // MIN
|
||||
Hatchet, // BTN
|
||||
FishingRod, // FSH
|
||||
|
||||
ClawHammer, // CRP Off
|
||||
File, // BSM Off
|
||||
Pliers, // ARM Off
|
||||
GrindingWheel, // GSM Off
|
||||
Awl, // LTW Off
|
||||
SpinningWheel, // WVR Off
|
||||
Mortar, // ALC Off
|
||||
CulinaryKnife, // CUL Off
|
||||
Sledgehammer, // MIN Off
|
||||
GardenScythe, // BTN Off
|
||||
Gig, // FSH Off
|
||||
}
|
||||
|
||||
public static class FullEquipTypeExtensions
|
||||
{
|
||||
internal static FullEquipType ToEquipType(this Item item)
|
||||
{
|
||||
var slot = (EquipSlot)item.EquipSlotCategory.Row;
|
||||
var weapon = (WeaponCategory)item.ItemUICategory.Row;
|
||||
return slot.ToEquipType(weapon);
|
||||
}
|
||||
|
||||
public static bool IsWeapon(this FullEquipType type)
|
||||
=> type switch
|
||||
{
|
||||
FullEquipType.Fists => true,
|
||||
FullEquipType.Sword => true,
|
||||
FullEquipType.Axe => true,
|
||||
FullEquipType.Bow => true,
|
||||
FullEquipType.Lance => true,
|
||||
FullEquipType.Staff => true,
|
||||
FullEquipType.Wand => true,
|
||||
FullEquipType.Book => true,
|
||||
FullEquipType.Daggers => true,
|
||||
FullEquipType.Broadsword => true,
|
||||
FullEquipType.Gun => true,
|
||||
FullEquipType.Orrery => true,
|
||||
FullEquipType.Katana => true,
|
||||
FullEquipType.Rapier => true,
|
||||
FullEquipType.Cane => true,
|
||||
FullEquipType.Gunblade => true,
|
||||
FullEquipType.Glaives => true,
|
||||
FullEquipType.Scythe => true,
|
||||
FullEquipType.Nouliths => true,
|
||||
FullEquipType.Shield => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsTool(this FullEquipType type)
|
||||
=> type switch
|
||||
{
|
||||
FullEquipType.Saw => true,
|
||||
FullEquipType.CrossPeinHammer => true,
|
||||
FullEquipType.RaisingHammer => true,
|
||||
FullEquipType.LapidaryHammer => true,
|
||||
FullEquipType.Knife => true,
|
||||
FullEquipType.Needle => true,
|
||||
FullEquipType.Alembic => true,
|
||||
FullEquipType.Frypan => true,
|
||||
FullEquipType.Pickaxe => true,
|
||||
FullEquipType.Hatchet => true,
|
||||
FullEquipType.FishingRod => true,
|
||||
FullEquipType.ClawHammer => true,
|
||||
FullEquipType.File => true,
|
||||
FullEquipType.Pliers => true,
|
||||
FullEquipType.GrindingWheel => true,
|
||||
FullEquipType.Awl => true,
|
||||
FullEquipType.SpinningWheel => true,
|
||||
FullEquipType.Mortar => true,
|
||||
FullEquipType.CulinaryKnife => true,
|
||||
FullEquipType.Sledgehammer => true,
|
||||
FullEquipType.GardenScythe => true,
|
||||
FullEquipType.Gig => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsEquipment(this FullEquipType type)
|
||||
=> type switch
|
||||
{
|
||||
FullEquipType.Head => true,
|
||||
FullEquipType.Body => true,
|
||||
FullEquipType.Hands => true,
|
||||
FullEquipType.Legs => true,
|
||||
FullEquipType.Feet => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsAccessory(this FullEquipType type)
|
||||
=> type switch
|
||||
{
|
||||
FullEquipType.Ears => true,
|
||||
FullEquipType.Neck => true,
|
||||
FullEquipType.Wrists => true,
|
||||
FullEquipType.Finger => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static string ToName(this FullEquipType type)
|
||||
=> type switch
|
||||
{
|
||||
FullEquipType.Head => EquipSlot.Head.ToName(),
|
||||
FullEquipType.Body => EquipSlot.Body.ToName(),
|
||||
FullEquipType.Hands => EquipSlot.Hands.ToName(),
|
||||
FullEquipType.Legs => EquipSlot.Legs.ToName(),
|
||||
FullEquipType.Feet => EquipSlot.Feet.ToName(),
|
||||
FullEquipType.Ears => EquipSlot.Ears.ToName(),
|
||||
FullEquipType.Neck => EquipSlot.Neck.ToName(),
|
||||
FullEquipType.Wrists => EquipSlot.Wrists.ToName(),
|
||||
FullEquipType.Finger => "Ring",
|
||||
FullEquipType.Fists => "Fist Weapon",
|
||||
FullEquipType.FistsOff => "Fist Weapon (Offhand)",
|
||||
FullEquipType.Sword => "Sword",
|
||||
FullEquipType.Axe => "Axe",
|
||||
FullEquipType.Bow => "Bow",
|
||||
FullEquipType.BowOff => "Quiver",
|
||||
FullEquipType.Lance => "Lance",
|
||||
FullEquipType.Staff => "Staff",
|
||||
FullEquipType.Wand => "Mace",
|
||||
FullEquipType.Book => "Book",
|
||||
FullEquipType.Daggers => "Dagger",
|
||||
FullEquipType.DaggersOff => "Dagger (Offhand)",
|
||||
FullEquipType.Broadsword => "Broadsword",
|
||||
FullEquipType.Gun => "Gun",
|
||||
FullEquipType.GunOff => "Aetherotransformer",
|
||||
FullEquipType.Orrery => "Orrery",
|
||||
FullEquipType.OrreryOff => "Card Holder",
|
||||
FullEquipType.Katana => "Katana",
|
||||
FullEquipType.KatanaOff => "Sheathe",
|
||||
FullEquipType.Rapier => "Rapier",
|
||||
FullEquipType.RapierOff => "Focus",
|
||||
FullEquipType.Cane => "Cane",
|
||||
FullEquipType.Gunblade => "Gunblade",
|
||||
FullEquipType.Glaives => "Glaive",
|
||||
FullEquipType.GlaivesOff => "Glaive (Offhand)",
|
||||
FullEquipType.Scythe => "Scythe",
|
||||
FullEquipType.Nouliths => "Nouliths",
|
||||
FullEquipType.Shield => "Shield",
|
||||
FullEquipType.Saw => "Saw",
|
||||
FullEquipType.CrossPeinHammer => "Cross Pein Hammer",
|
||||
FullEquipType.RaisingHammer => "Raising Hammer",
|
||||
FullEquipType.LapidaryHammer => "Lapidary Hammer",
|
||||
FullEquipType.Knife => "Round Knife",
|
||||
FullEquipType.Needle => "Needle",
|
||||
FullEquipType.Alembic => "Alembic",
|
||||
FullEquipType.Frypan => "Frypan",
|
||||
FullEquipType.Pickaxe => "Pickaxe",
|
||||
FullEquipType.Hatchet => "Hatchet",
|
||||
FullEquipType.FishingRod => "Fishing Rod",
|
||||
FullEquipType.ClawHammer => "Clawhammer",
|
||||
FullEquipType.File => "File",
|
||||
FullEquipType.Pliers => "Pliers",
|
||||
FullEquipType.GrindingWheel => "Grinding Wheel",
|
||||
FullEquipType.Awl => "Awl",
|
||||
FullEquipType.SpinningWheel => "Spinning Wheel",
|
||||
FullEquipType.Mortar => "Mortar",
|
||||
FullEquipType.CulinaryKnife => "Culinary Knife",
|
||||
FullEquipType.Sledgehammer => "Sledgehammer",
|
||||
FullEquipType.GardenScythe => "Garden Scythe",
|
||||
FullEquipType.Gig => "Gig",
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
public static EquipSlot ToSlot(this FullEquipType type)
|
||||
=> type switch
|
||||
{
|
||||
FullEquipType.Head => EquipSlot.Head,
|
||||
FullEquipType.Body => EquipSlot.Body,
|
||||
FullEquipType.Hands => EquipSlot.Hands,
|
||||
FullEquipType.Legs => EquipSlot.Legs,
|
||||
FullEquipType.Feet => EquipSlot.Feet,
|
||||
FullEquipType.Ears => EquipSlot.Ears,
|
||||
FullEquipType.Neck => EquipSlot.Neck,
|
||||
FullEquipType.Wrists => EquipSlot.Wrists,
|
||||
FullEquipType.Finger => EquipSlot.RFinger,
|
||||
FullEquipType.Fists => EquipSlot.MainHand,
|
||||
FullEquipType.FistsOff => EquipSlot.OffHand,
|
||||
FullEquipType.Sword => EquipSlot.MainHand,
|
||||
FullEquipType.Axe => EquipSlot.MainHand,
|
||||
FullEquipType.Bow => EquipSlot.MainHand,
|
||||
FullEquipType.BowOff => EquipSlot.OffHand,
|
||||
FullEquipType.Lance => EquipSlot.MainHand,
|
||||
FullEquipType.Staff => EquipSlot.MainHand,
|
||||
FullEquipType.Wand => EquipSlot.MainHand,
|
||||
FullEquipType.Book => EquipSlot.MainHand,
|
||||
FullEquipType.Daggers => EquipSlot.MainHand,
|
||||
FullEquipType.DaggersOff => EquipSlot.OffHand,
|
||||
FullEquipType.Broadsword => EquipSlot.MainHand,
|
||||
FullEquipType.Gun => EquipSlot.MainHand,
|
||||
FullEquipType.GunOff => EquipSlot.OffHand,
|
||||
FullEquipType.Orrery => EquipSlot.MainHand,
|
||||
FullEquipType.OrreryOff => EquipSlot.OffHand,
|
||||
FullEquipType.Katana => EquipSlot.MainHand,
|
||||
FullEquipType.KatanaOff => EquipSlot.OffHand,
|
||||
FullEquipType.Rapier => EquipSlot.MainHand,
|
||||
FullEquipType.RapierOff => EquipSlot.OffHand,
|
||||
FullEquipType.Cane => EquipSlot.MainHand,
|
||||
FullEquipType.Gunblade => EquipSlot.MainHand,
|
||||
FullEquipType.Glaives => EquipSlot.MainHand,
|
||||
FullEquipType.GlaivesOff => EquipSlot.OffHand,
|
||||
FullEquipType.Scythe => EquipSlot.MainHand,
|
||||
FullEquipType.Nouliths => EquipSlot.MainHand,
|
||||
FullEquipType.Shield => EquipSlot.OffHand,
|
||||
FullEquipType.Saw => EquipSlot.MainHand,
|
||||
FullEquipType.CrossPeinHammer => EquipSlot.MainHand,
|
||||
FullEquipType.RaisingHammer => EquipSlot.MainHand,
|
||||
FullEquipType.LapidaryHammer => EquipSlot.MainHand,
|
||||
FullEquipType.Knife => EquipSlot.MainHand,
|
||||
FullEquipType.Needle => EquipSlot.MainHand,
|
||||
FullEquipType.Alembic => EquipSlot.MainHand,
|
||||
FullEquipType.Frypan => EquipSlot.MainHand,
|
||||
FullEquipType.Pickaxe => EquipSlot.MainHand,
|
||||
FullEquipType.Hatchet => EquipSlot.MainHand,
|
||||
FullEquipType.FishingRod => EquipSlot.MainHand,
|
||||
FullEquipType.ClawHammer => EquipSlot.OffHand,
|
||||
FullEquipType.File => EquipSlot.OffHand,
|
||||
FullEquipType.Pliers => EquipSlot.OffHand,
|
||||
FullEquipType.GrindingWheel => EquipSlot.OffHand,
|
||||
FullEquipType.Awl => EquipSlot.OffHand,
|
||||
FullEquipType.SpinningWheel => EquipSlot.OffHand,
|
||||
FullEquipType.Mortar => EquipSlot.OffHand,
|
||||
FullEquipType.CulinaryKnife => EquipSlot.OffHand,
|
||||
FullEquipType.Sledgehammer => EquipSlot.OffHand,
|
||||
FullEquipType.GardenScythe => EquipSlot.OffHand,
|
||||
FullEquipType.Gig => EquipSlot.OffHand,
|
||||
_ => EquipSlot.Unknown,
|
||||
};
|
||||
|
||||
public static FullEquipType ToEquipType(this EquipSlot slot, WeaponCategory category = WeaponCategory.Unknown, bool mainhand = true)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.Head => FullEquipType.Head,
|
||||
EquipSlot.Body => FullEquipType.Body,
|
||||
EquipSlot.Hands => FullEquipType.Hands,
|
||||
EquipSlot.Legs => FullEquipType.Legs,
|
||||
EquipSlot.Feet => FullEquipType.Feet,
|
||||
EquipSlot.Ears => FullEquipType.Ears,
|
||||
EquipSlot.Neck => FullEquipType.Neck,
|
||||
EquipSlot.Wrists => FullEquipType.Wrists,
|
||||
EquipSlot.RFinger => FullEquipType.Finger,
|
||||
EquipSlot.LFinger => FullEquipType.Finger,
|
||||
EquipSlot.HeadBody => FullEquipType.Body,
|
||||
EquipSlot.BodyHandsLegsFeet => FullEquipType.Body,
|
||||
EquipSlot.LegsFeet => FullEquipType.Legs,
|
||||
EquipSlot.FullBody => FullEquipType.Body,
|
||||
EquipSlot.BodyHands => FullEquipType.Body,
|
||||
EquipSlot.BodyLegsFeet => FullEquipType.Body,
|
||||
EquipSlot.ChestHands => FullEquipType.Body,
|
||||
EquipSlot.MainHand => category.ToEquipType(mainhand),
|
||||
EquipSlot.OffHand => category.ToEquipType(mainhand),
|
||||
EquipSlot.BothHand => category.ToEquipType(mainhand),
|
||||
_ => FullEquipType.Unknown,
|
||||
};
|
||||
|
||||
public static FullEquipType ToEquipType(this WeaponCategory category, bool mainhand = true)
|
||||
=> category switch
|
||||
{
|
||||
WeaponCategory.Pugilist when mainhand => FullEquipType.Fists,
|
||||
WeaponCategory.Pugilist => FullEquipType.FistsOff,
|
||||
WeaponCategory.Gladiator => FullEquipType.Sword,
|
||||
WeaponCategory.Marauder => FullEquipType.Axe,
|
||||
WeaponCategory.Archer when mainhand => FullEquipType.Bow,
|
||||
WeaponCategory.Archer => FullEquipType.BowOff,
|
||||
WeaponCategory.Lancer => FullEquipType.Lance,
|
||||
WeaponCategory.Thaumaturge1 => FullEquipType.Wand,
|
||||
WeaponCategory.Thaumaturge2 => FullEquipType.Staff,
|
||||
WeaponCategory.Conjurer1 => FullEquipType.Wand,
|
||||
WeaponCategory.Conjurer2 => FullEquipType.Staff,
|
||||
WeaponCategory.Arcanist => FullEquipType.Book,
|
||||
WeaponCategory.Shield => FullEquipType.Shield,
|
||||
WeaponCategory.CarpenterMain => FullEquipType.Saw,
|
||||
WeaponCategory.CarpenterOff => FullEquipType.ClawHammer,
|
||||
WeaponCategory.BlacksmithMain => FullEquipType.CrossPeinHammer,
|
||||
WeaponCategory.BlacksmithOff => FullEquipType.File,
|
||||
WeaponCategory.ArmorerMain => FullEquipType.RaisingHammer,
|
||||
WeaponCategory.ArmorerOff => FullEquipType.Pliers,
|
||||
WeaponCategory.GoldsmithMain => FullEquipType.LapidaryHammer,
|
||||
WeaponCategory.GoldsmithOff => FullEquipType.GrindingWheel,
|
||||
WeaponCategory.LeatherworkerMain => FullEquipType.Knife,
|
||||
WeaponCategory.LeatherworkerOff => FullEquipType.Awl,
|
||||
WeaponCategory.WeaverMain => FullEquipType.Needle,
|
||||
WeaponCategory.WeaverOff => FullEquipType.SpinningWheel,
|
||||
WeaponCategory.AlchemistMain => FullEquipType.Alembic,
|
||||
WeaponCategory.AlchemistOff => FullEquipType.Mortar,
|
||||
WeaponCategory.CulinarianMain => FullEquipType.Frypan,
|
||||
WeaponCategory.CulinarianOff => FullEquipType.CulinaryKnife,
|
||||
WeaponCategory.MinerMain => FullEquipType.Pickaxe,
|
||||
WeaponCategory.MinerOff => FullEquipType.Sledgehammer,
|
||||
WeaponCategory.BotanistMain => FullEquipType.Hatchet,
|
||||
WeaponCategory.BotanistOff => FullEquipType.GardenScythe,
|
||||
WeaponCategory.FisherMain => FullEquipType.FishingRod,
|
||||
WeaponCategory.FisherOff => FullEquipType.Gig,
|
||||
WeaponCategory.Rogue when mainhand => FullEquipType.Daggers,
|
||||
WeaponCategory.Rogue => FullEquipType.DaggersOff,
|
||||
WeaponCategory.DarkKnight => FullEquipType.Broadsword,
|
||||
WeaponCategory.Machinist when mainhand => FullEquipType.Gun,
|
||||
WeaponCategory.Machinist => FullEquipType.GunOff,
|
||||
WeaponCategory.Astrologian when mainhand => FullEquipType.Orrery,
|
||||
WeaponCategory.Astrologian => FullEquipType.OrreryOff,
|
||||
WeaponCategory.Samurai when mainhand => FullEquipType.Katana,
|
||||
WeaponCategory.Samurai => FullEquipType.KatanaOff,
|
||||
WeaponCategory.RedMage when mainhand => FullEquipType.Rapier,
|
||||
WeaponCategory.RedMage => FullEquipType.RapierOff,
|
||||
WeaponCategory.Scholar => FullEquipType.Book,
|
||||
WeaponCategory.BlueMage => FullEquipType.Cane,
|
||||
WeaponCategory.Gunbreaker => FullEquipType.Gunblade,
|
||||
WeaponCategory.Dancer when mainhand => FullEquipType.Glaives,
|
||||
WeaponCategory.Dancer => FullEquipType.GlaivesOff,
|
||||
WeaponCategory.Reaper => FullEquipType.Scythe,
|
||||
WeaponCategory.Sage => FullEquipType.Nouliths,
|
||||
_ => FullEquipType.Unknown,
|
||||
};
|
||||
|
||||
public static FullEquipType ValidOffhand(this FullEquipType type)
|
||||
=> type switch
|
||||
{
|
||||
FullEquipType.Fists => FullEquipType.FistsOff,
|
||||
FullEquipType.Sword => FullEquipType.Shield,
|
||||
FullEquipType.Wand => FullEquipType.Shield,
|
||||
FullEquipType.Daggers => FullEquipType.DaggersOff,
|
||||
FullEquipType.Gun => FullEquipType.GunOff,
|
||||
FullEquipType.Orrery => FullEquipType.OrreryOff,
|
||||
FullEquipType.Rapier => FullEquipType.RapierOff,
|
||||
FullEquipType.Glaives => FullEquipType.GlaivesOff,
|
||||
FullEquipType.Bow => FullEquipType.BowOff,
|
||||
FullEquipType.Katana => FullEquipType.KatanaOff,
|
||||
_ => FullEquipType.Unknown,
|
||||
};
|
||||
|
||||
public static FullEquipType Offhand(this FullEquipType type)
|
||||
=> type switch
|
||||
{
|
||||
FullEquipType.Fists => FullEquipType.FistsOff,
|
||||
FullEquipType.Sword => FullEquipType.Shield,
|
||||
FullEquipType.Wand => FullEquipType.Shield,
|
||||
FullEquipType.Daggers => FullEquipType.DaggersOff,
|
||||
FullEquipType.Gun => FullEquipType.GunOff,
|
||||
FullEquipType.Orrery => FullEquipType.OrreryOff,
|
||||
FullEquipType.Rapier => FullEquipType.RapierOff,
|
||||
FullEquipType.Glaives => FullEquipType.GlaivesOff,
|
||||
FullEquipType.Bow => FullEquipType.BowOff,
|
||||
FullEquipType.Katana => FullEquipType.KatanaOff,
|
||||
FullEquipType.Saw => FullEquipType.ClawHammer,
|
||||
FullEquipType.CrossPeinHammer => FullEquipType.File,
|
||||
FullEquipType.RaisingHammer => FullEquipType.Pliers,
|
||||
FullEquipType.LapidaryHammer => FullEquipType.GrindingWheel,
|
||||
FullEquipType.Knife => FullEquipType.Awl,
|
||||
FullEquipType.Needle => FullEquipType.SpinningWheel,
|
||||
FullEquipType.Alembic => FullEquipType.Mortar,
|
||||
FullEquipType.Frypan => FullEquipType.CulinaryKnife,
|
||||
FullEquipType.Pickaxe => FullEquipType.Sledgehammer,
|
||||
FullEquipType.Hatchet => FullEquipType.GardenScythe,
|
||||
FullEquipType.FishingRod => FullEquipType.Gig,
|
||||
_ => FullEquipType.Unknown,
|
||||
};
|
||||
|
||||
internal static string OffhandTypeSuffix(this FullEquipType type)
|
||||
=> type switch
|
||||
{
|
||||
FullEquipType.FistsOff => " (Offhand)",
|
||||
FullEquipType.DaggersOff => " (Offhand)",
|
||||
FullEquipType.GunOff => " (Aetherotransformer)",
|
||||
FullEquipType.OrreryOff => " (Card Holder)",
|
||||
FullEquipType.RapierOff => " (Focus)",
|
||||
FullEquipType.GlaivesOff => " (Offhand)",
|
||||
FullEquipType.BowOff => " (Quiver)",
|
||||
FullEquipType.KatanaOff => " (Sheathe)",
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
public static bool IsOffhandType(this FullEquipType type)
|
||||
=> type.OffhandTypeSuffix().Length > 0;
|
||||
|
||||
public static readonly IReadOnlyList<FullEquipType> WeaponTypes
|
||||
= Enum.GetValues<FullEquipType>().Where(v => v.IsWeapon()).ToArray();
|
||||
|
||||
public static readonly IReadOnlyList<FullEquipType> ToolTypes
|
||||
= Enum.GetValues<FullEquipType>().Where(v => v.IsTool()).ToArray();
|
||||
|
||||
public static readonly IReadOnlyList<FullEquipType> EquipmentTypes
|
||||
= Enum.GetValues<FullEquipType>().Where(v => v.IsEquipment()).ToArray();
|
||||
|
||||
public static readonly IReadOnlyList<FullEquipType> AccessoryTypes
|
||||
= Enum.GetValues<FullEquipType>().Where(v => v.IsAccessory()).ToArray();
|
||||
|
||||
public static readonly IReadOnlyList<FullEquipType> OffhandTypes
|
||||
= Enum.GetValues<FullEquipType>().Where(v => v.OffhandTypeSuffix().Length > 0).ToArray();
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
namespace Penumbra.GameData.Enums;
|
||||
|
||||
public enum ObjectType : byte
|
||||
{
|
||||
Unknown,
|
||||
Vfx,
|
||||
DemiHuman,
|
||||
Accessory,
|
||||
World,
|
||||
Housing,
|
||||
Monster,
|
||||
Icon,
|
||||
LoadingScreen,
|
||||
Map,
|
||||
Interface,
|
||||
Equipment,
|
||||
Character,
|
||||
Weapon,
|
||||
Font,
|
||||
}
|
||||
|
||||
public static class ObjectTypeExtensions
|
||||
{
|
||||
public static string ToName( this ObjectType type )
|
||||
=> type switch
|
||||
{
|
||||
ObjectType.Vfx => "Visual Effect",
|
||||
ObjectType.DemiHuman => "Demi Human",
|
||||
ObjectType.Accessory => "Accessory",
|
||||
ObjectType.World => "Doodad",
|
||||
ObjectType.Housing => "Housing Object",
|
||||
ObjectType.Monster => "Monster",
|
||||
ObjectType.Icon => "Icon",
|
||||
ObjectType.LoadingScreen => "Loading Screen",
|
||||
ObjectType.Map => "Map",
|
||||
ObjectType.Interface => "UI Element",
|
||||
ObjectType.Equipment => "Equipment",
|
||||
ObjectType.Character => "Character",
|
||||
ObjectType.Weapon => "Weapon",
|
||||
ObjectType.Font => "Font",
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
|
||||
public static readonly ObjectType[] ValidImcTypes =
|
||||
{
|
||||
ObjectType.Equipment,
|
||||
ObjectType.Accessory,
|
||||
ObjectType.DemiHuman,
|
||||
ObjectType.Monster,
|
||||
ObjectType.Weapon,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,510 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using static Penumbra.GameData.Enums.GenderRace;
|
||||
|
||||
namespace Penumbra.GameData.Enums;
|
||||
|
||||
public enum Race : byte
|
||||
{
|
||||
Unknown,
|
||||
Hyur,
|
||||
Elezen,
|
||||
Lalafell,
|
||||
Miqote,
|
||||
Roegadyn,
|
||||
AuRa,
|
||||
Hrothgar,
|
||||
Viera,
|
||||
}
|
||||
|
||||
public enum Gender : byte
|
||||
{
|
||||
Unknown,
|
||||
Male,
|
||||
Female,
|
||||
MaleNpc,
|
||||
FemaleNpc,
|
||||
}
|
||||
|
||||
public enum ModelRace : byte
|
||||
{
|
||||
Unknown,
|
||||
Midlander,
|
||||
Highlander,
|
||||
Elezen,
|
||||
Lalafell,
|
||||
Miqote,
|
||||
Roegadyn,
|
||||
AuRa,
|
||||
Hrothgar,
|
||||
Viera,
|
||||
}
|
||||
|
||||
public enum SubRace : byte
|
||||
{
|
||||
Unknown,
|
||||
Midlander,
|
||||
Highlander,
|
||||
Wildwood,
|
||||
Duskwight,
|
||||
Plainsfolk,
|
||||
Dunesfolk,
|
||||
SeekerOfTheSun,
|
||||
KeeperOfTheMoon,
|
||||
Seawolf,
|
||||
Hellsguard,
|
||||
Raen,
|
||||
Xaela,
|
||||
Helion,
|
||||
Lost,
|
||||
Rava,
|
||||
Veena,
|
||||
}
|
||||
|
||||
// The combined gender-race-npc numerical code as used by the game.
|
||||
public enum GenderRace : ushort
|
||||
{
|
||||
Unknown = 0,
|
||||
MidlanderMale = 0101,
|
||||
MidlanderMaleNpc = 0104,
|
||||
MidlanderFemale = 0201,
|
||||
MidlanderFemaleNpc = 0204,
|
||||
HighlanderMale = 0301,
|
||||
HighlanderMaleNpc = 0304,
|
||||
HighlanderFemale = 0401,
|
||||
HighlanderFemaleNpc = 0404,
|
||||
ElezenMale = 0501,
|
||||
ElezenMaleNpc = 0504,
|
||||
ElezenFemale = 0601,
|
||||
ElezenFemaleNpc = 0604,
|
||||
MiqoteMale = 0701,
|
||||
MiqoteMaleNpc = 0704,
|
||||
MiqoteFemale = 0801,
|
||||
MiqoteFemaleNpc = 0804,
|
||||
RoegadynMale = 0901,
|
||||
RoegadynMaleNpc = 0904,
|
||||
RoegadynFemale = 1001,
|
||||
RoegadynFemaleNpc = 1004,
|
||||
LalafellMale = 1101,
|
||||
LalafellMaleNpc = 1104,
|
||||
LalafellFemale = 1201,
|
||||
LalafellFemaleNpc = 1204,
|
||||
AuRaMale = 1301,
|
||||
AuRaMaleNpc = 1304,
|
||||
AuRaFemale = 1401,
|
||||
AuRaFemaleNpc = 1404,
|
||||
HrothgarMale = 1501,
|
||||
HrothgarMaleNpc = 1504,
|
||||
HrothgarFemale = 1601,
|
||||
HrothgarFemaleNpc = 1604,
|
||||
VieraMale = 1701,
|
||||
VieraMaleNpc = 1704,
|
||||
VieraFemale = 1801,
|
||||
VieraFemaleNpc = 1804,
|
||||
UnknownMaleNpc = 9104,
|
||||
UnknownFemaleNpc = 9204,
|
||||
}
|
||||
|
||||
public static class RaceEnumExtensions
|
||||
{
|
||||
public static Race ToRace(this ModelRace race)
|
||||
{
|
||||
return race switch
|
||||
{
|
||||
ModelRace.Unknown => Race.Unknown,
|
||||
ModelRace.Midlander => Race.Hyur,
|
||||
ModelRace.Highlander => Race.Hyur,
|
||||
ModelRace.Elezen => Race.Elezen,
|
||||
ModelRace.Lalafell => Race.Lalafell,
|
||||
ModelRace.Miqote => Race.Miqote,
|
||||
ModelRace.Roegadyn => Race.Roegadyn,
|
||||
ModelRace.AuRa => Race.AuRa,
|
||||
ModelRace.Hrothgar => Race.Hrothgar,
|
||||
ModelRace.Viera => Race.Viera,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(race), race, null),
|
||||
};
|
||||
}
|
||||
|
||||
public static Race ToRace(this SubRace subRace)
|
||||
{
|
||||
return subRace switch
|
||||
{
|
||||
SubRace.Unknown => Race.Unknown,
|
||||
SubRace.Midlander => Race.Hyur,
|
||||
SubRace.Highlander => Race.Hyur,
|
||||
SubRace.Wildwood => Race.Elezen,
|
||||
SubRace.Duskwight => Race.Elezen,
|
||||
SubRace.Plainsfolk => Race.Lalafell,
|
||||
SubRace.Dunesfolk => Race.Lalafell,
|
||||
SubRace.SeekerOfTheSun => Race.Miqote,
|
||||
SubRace.KeeperOfTheMoon => Race.Miqote,
|
||||
SubRace.Seawolf => Race.Roegadyn,
|
||||
SubRace.Hellsguard => Race.Roegadyn,
|
||||
SubRace.Raen => Race.AuRa,
|
||||
SubRace.Xaela => Race.AuRa,
|
||||
SubRace.Helion => Race.Hrothgar,
|
||||
SubRace.Lost => Race.Hrothgar,
|
||||
SubRace.Rava => Race.Viera,
|
||||
SubRace.Veena => Race.Viera,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(subRace), subRace, null),
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToName(this ModelRace modelRace)
|
||||
{
|
||||
return modelRace switch
|
||||
{
|
||||
ModelRace.Midlander => SubRace.Midlander.ToName(),
|
||||
ModelRace.Highlander => SubRace.Highlander.ToName(),
|
||||
ModelRace.Elezen => Race.Elezen.ToName(),
|
||||
ModelRace.Lalafell => Race.Lalafell.ToName(),
|
||||
ModelRace.Miqote => Race.Miqote.ToName(),
|
||||
ModelRace.Roegadyn => Race.Roegadyn.ToName(),
|
||||
ModelRace.AuRa => Race.AuRa.ToName(),
|
||||
ModelRace.Hrothgar => Race.Hrothgar.ToName(),
|
||||
ModelRace.Viera => Race.Viera.ToName(),
|
||||
_ => Race.Unknown.ToName(),
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToName(this Race race)
|
||||
{
|
||||
return race switch
|
||||
{
|
||||
Race.Hyur => "Hyur",
|
||||
Race.Elezen => "Elezen",
|
||||
Race.Lalafell => "Lalafell",
|
||||
Race.Miqote => "Miqo'te",
|
||||
Race.Roegadyn => "Roegadyn",
|
||||
Race.AuRa => "Au Ra",
|
||||
Race.Hrothgar => "Hrothgar",
|
||||
Race.Viera => "Viera",
|
||||
_ => "Unknown",
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToName(this Gender gender)
|
||||
{
|
||||
return gender switch
|
||||
{
|
||||
Gender.Male => "Male",
|
||||
Gender.Female => "Female",
|
||||
Gender.MaleNpc => "Male (NPC)",
|
||||
Gender.FemaleNpc => "Female (NPC)",
|
||||
_ => "Unknown",
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToName(this SubRace subRace)
|
||||
{
|
||||
return subRace switch
|
||||
{
|
||||
SubRace.Midlander => "Midlander",
|
||||
SubRace.Highlander => "Highlander",
|
||||
SubRace.Wildwood => "Wildwood",
|
||||
SubRace.Duskwight => "Duskwight",
|
||||
SubRace.Plainsfolk => "Plainsfolk",
|
||||
SubRace.Dunesfolk => "Dunesfolk",
|
||||
SubRace.SeekerOfTheSun => "Seeker Of The Sun",
|
||||
SubRace.KeeperOfTheMoon => "Keeper Of The Moon",
|
||||
SubRace.Seawolf => "Seawolf",
|
||||
SubRace.Hellsguard => "Hellsguard",
|
||||
SubRace.Raen => "Raen",
|
||||
SubRace.Xaela => "Xaela",
|
||||
SubRace.Helion => "Hellion",
|
||||
SubRace.Lost => "Lost",
|
||||
SubRace.Rava => "Rava",
|
||||
SubRace.Veena => "Veena",
|
||||
_ => "Unknown",
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToShortName(this SubRace subRace)
|
||||
{
|
||||
return subRace switch
|
||||
{
|
||||
SubRace.SeekerOfTheSun => "Sunseeker",
|
||||
SubRace.KeeperOfTheMoon => "Moonkeeper",
|
||||
_ => subRace.ToName(),
|
||||
};
|
||||
}
|
||||
|
||||
public static bool FitsRace(this SubRace subRace, Race race)
|
||||
=> subRace.ToRace() == race;
|
||||
|
||||
public static byte ToByte(this Gender gender, ModelRace modelRace)
|
||||
=> (byte)((int)gender | ((int)modelRace << 3));
|
||||
|
||||
public static byte ToByte(this ModelRace modelRace, Gender gender)
|
||||
=> gender.ToByte(modelRace);
|
||||
|
||||
public static byte ToByte(this GenderRace value)
|
||||
{
|
||||
var (gender, race) = value.Split();
|
||||
return gender.ToByte(race);
|
||||
}
|
||||
|
||||
public static (Gender, ModelRace) Split(this GenderRace value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
Unknown => (Gender.Unknown, ModelRace.Unknown),
|
||||
MidlanderMale => (Gender.Male, ModelRace.Midlander),
|
||||
MidlanderMaleNpc => (Gender.MaleNpc, ModelRace.Midlander),
|
||||
MidlanderFemale => (Gender.Female, ModelRace.Midlander),
|
||||
MidlanderFemaleNpc => (Gender.FemaleNpc, ModelRace.Midlander),
|
||||
HighlanderMale => (Gender.Male, ModelRace.Highlander),
|
||||
HighlanderMaleNpc => (Gender.MaleNpc, ModelRace.Highlander),
|
||||
HighlanderFemale => (Gender.Female, ModelRace.Highlander),
|
||||
HighlanderFemaleNpc => (Gender.FemaleNpc, ModelRace.Highlander),
|
||||
ElezenMale => (Gender.Male, ModelRace.Elezen),
|
||||
ElezenMaleNpc => (Gender.MaleNpc, ModelRace.Elezen),
|
||||
ElezenFemale => (Gender.Female, ModelRace.Elezen),
|
||||
ElezenFemaleNpc => (Gender.FemaleNpc, ModelRace.Elezen),
|
||||
LalafellMale => (Gender.Male, ModelRace.Lalafell),
|
||||
LalafellMaleNpc => (Gender.MaleNpc, ModelRace.Lalafell),
|
||||
LalafellFemale => (Gender.Female, ModelRace.Lalafell),
|
||||
LalafellFemaleNpc => (Gender.FemaleNpc, ModelRace.Lalafell),
|
||||
MiqoteMale => (Gender.Male, ModelRace.Miqote),
|
||||
MiqoteMaleNpc => (Gender.MaleNpc, ModelRace.Miqote),
|
||||
MiqoteFemale => (Gender.Female, ModelRace.Miqote),
|
||||
MiqoteFemaleNpc => (Gender.FemaleNpc, ModelRace.Miqote),
|
||||
RoegadynMale => (Gender.Male, ModelRace.Roegadyn),
|
||||
RoegadynMaleNpc => (Gender.MaleNpc, ModelRace.Roegadyn),
|
||||
RoegadynFemale => (Gender.Female, ModelRace.Roegadyn),
|
||||
RoegadynFemaleNpc => (Gender.FemaleNpc, ModelRace.Roegadyn),
|
||||
AuRaMale => (Gender.Male, ModelRace.AuRa),
|
||||
AuRaMaleNpc => (Gender.MaleNpc, ModelRace.AuRa),
|
||||
AuRaFemale => (Gender.Female, ModelRace.AuRa),
|
||||
AuRaFemaleNpc => (Gender.FemaleNpc, ModelRace.AuRa),
|
||||
HrothgarMale => (Gender.Male, ModelRace.Hrothgar),
|
||||
HrothgarMaleNpc => (Gender.MaleNpc, ModelRace.Hrothgar),
|
||||
HrothgarFemale => (Gender.Female, ModelRace.Hrothgar),
|
||||
HrothgarFemaleNpc => (Gender.FemaleNpc, ModelRace.Hrothgar),
|
||||
VieraMale => (Gender.Male, ModelRace.Viera),
|
||||
VieraMaleNpc => (Gender.Male, ModelRace.Viera),
|
||||
VieraFemale => (Gender.Female, ModelRace.Viera),
|
||||
VieraFemaleNpc => (Gender.FemaleNpc, ModelRace.Viera),
|
||||
UnknownMaleNpc => (Gender.MaleNpc, ModelRace.Unknown),
|
||||
UnknownFemaleNpc => (Gender.FemaleNpc, ModelRace.Unknown),
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsValid(this GenderRace value)
|
||||
=> value != Unknown && Enum.IsDefined(typeof(GenderRace), value);
|
||||
|
||||
public static string ToRaceCode(this GenderRace value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
MidlanderMale => "0101",
|
||||
MidlanderMaleNpc => "0104",
|
||||
MidlanderFemale => "0201",
|
||||
MidlanderFemaleNpc => "0204",
|
||||
HighlanderMale => "0301",
|
||||
HighlanderMaleNpc => "0304",
|
||||
HighlanderFemale => "0401",
|
||||
HighlanderFemaleNpc => "0404",
|
||||
ElezenMale => "0501",
|
||||
ElezenMaleNpc => "0504",
|
||||
ElezenFemale => "0601",
|
||||
ElezenFemaleNpc => "0604",
|
||||
MiqoteMale => "0701",
|
||||
MiqoteMaleNpc => "0704",
|
||||
MiqoteFemale => "0801",
|
||||
MiqoteFemaleNpc => "0804",
|
||||
RoegadynMale => "0901",
|
||||
RoegadynMaleNpc => "0904",
|
||||
RoegadynFemale => "1001",
|
||||
RoegadynFemaleNpc => "1004",
|
||||
LalafellMale => "1101",
|
||||
LalafellMaleNpc => "1104",
|
||||
LalafellFemale => "1201",
|
||||
LalafellFemaleNpc => "1204",
|
||||
AuRaMale => "1301",
|
||||
AuRaMaleNpc => "1304",
|
||||
AuRaFemale => "1401",
|
||||
AuRaFemaleNpc => "1404",
|
||||
HrothgarMale => "1501",
|
||||
HrothgarMaleNpc => "1504",
|
||||
HrothgarFemale => "1601",
|
||||
HrothgarFemaleNpc => "1604",
|
||||
VieraMale => "1701",
|
||||
VieraMaleNpc => "1704",
|
||||
VieraFemale => "1801",
|
||||
VieraFemaleNpc => "1804",
|
||||
UnknownMaleNpc => "9104",
|
||||
UnknownFemaleNpc => "9204",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
public static GenderRace[] Dependencies(this GenderRace raceCode)
|
||||
=> DependencyList.TryGetValue(raceCode, out var dep) ? dep : Array.Empty<GenderRace>();
|
||||
|
||||
public static IEnumerable<GenderRace> OnlyDependencies(this GenderRace raceCode)
|
||||
=> DependencyList.TryGetValue(raceCode, out var dep) ? dep.Skip(1) : Array.Empty<GenderRace>();
|
||||
|
||||
private static readonly Dictionary<GenderRace, GenderRace[]> DependencyList = new()
|
||||
{
|
||||
// @formatter:off
|
||||
[MidlanderMale] = new[]{ MidlanderMale },
|
||||
[HighlanderMale] = new[]{ HighlanderMale, MidlanderMale },
|
||||
[ElezenMale] = new[]{ ElezenMale, MidlanderMale },
|
||||
[MiqoteMale] = new[]{ MiqoteMale, MidlanderMale },
|
||||
[RoegadynMale] = new[]{ RoegadynMale, MidlanderMale },
|
||||
[LalafellMale] = new[]{ LalafellMale, MidlanderMale },
|
||||
[AuRaMale] = new[]{ AuRaMale, MidlanderMale },
|
||||
[HrothgarMale] = new[]{ HrothgarMale, RoegadynMale, MidlanderMale },
|
||||
[VieraMale] = new[]{ VieraMale, MidlanderMale },
|
||||
[MidlanderFemale] = new[]{ MidlanderFemale, MidlanderMale },
|
||||
[HighlanderFemale] = new[]{ HighlanderFemale, MidlanderFemale, MidlanderMale },
|
||||
[ElezenFemale] = new[]{ ElezenFemale, MidlanderFemale, MidlanderMale },
|
||||
[MiqoteFemale] = new[]{ MiqoteFemale, MidlanderFemale, MidlanderMale },
|
||||
[RoegadynFemale] = new[]{ RoegadynFemale, MidlanderFemale, MidlanderMale },
|
||||
[LalafellFemale] = new[]{ LalafellFemale, LalafellMale, MidlanderMale },
|
||||
[AuRaFemale] = new[]{ AuRaFemale, MidlanderFemale, MidlanderMale },
|
||||
[HrothgarFemale] = new[]{ HrothgarFemale, RoegadynFemale, MidlanderFemale, MidlanderMale },
|
||||
[VieraFemale] = new[]{ VieraFemale, MidlanderFemale, MidlanderMale },
|
||||
[MidlanderMaleNpc] = new[]{ MidlanderMaleNpc, MidlanderMale },
|
||||
[HighlanderMaleNpc] = new[]{ HighlanderMaleNpc, HighlanderMale, MidlanderMaleNpc, MidlanderMale },
|
||||
[ElezenMaleNpc] = new[]{ ElezenMaleNpc, ElezenMale, MidlanderMaleNpc, MidlanderMale },
|
||||
[MiqoteMaleNpc] = new[]{ MiqoteMaleNpc, MiqoteMale, MidlanderMaleNpc, MidlanderMale },
|
||||
[RoegadynMaleNpc] = new[]{ RoegadynMaleNpc, RoegadynMale, MidlanderMaleNpc, MidlanderMale },
|
||||
[LalafellMaleNpc] = new[]{ LalafellMaleNpc, LalafellMale, MidlanderMaleNpc, MidlanderMale },
|
||||
[AuRaMaleNpc] = new[]{ AuRaMaleNpc, AuRaMale, MidlanderMaleNpc, MidlanderMale },
|
||||
[HrothgarMaleNpc] = new[]{ HrothgarMaleNpc, HrothgarMale, RoegadynMaleNpc, RoegadynMale, MidlanderMaleNpc, MidlanderMale },
|
||||
[VieraMaleNpc] = new[]{ VieraMaleNpc, VieraMale, MidlanderMaleNpc, MidlanderMale },
|
||||
[MidlanderFemaleNpc] = new[]{ MidlanderFemaleNpc, MidlanderFemale, MidlanderMaleNpc, MidlanderMale },
|
||||
[HighlanderFemaleNpc] = new[]{ HighlanderFemaleNpc, HighlanderFemale, MidlanderFemaleNpc, MidlanderFemale, MidlanderMaleNpc, MidlanderMale },
|
||||
[ElezenFemaleNpc] = new[]{ ElezenFemaleNpc, ElezenFemale, MidlanderFemaleNpc, MidlanderFemale, MidlanderMaleNpc, MidlanderMale },
|
||||
[MiqoteFemaleNpc] = new[]{ MiqoteFemaleNpc, MiqoteFemale, MidlanderFemaleNpc, MidlanderFemale, MidlanderMaleNpc, MidlanderMale },
|
||||
[RoegadynFemaleNpc] = new[]{ RoegadynFemaleNpc, RoegadynFemale, MidlanderFemaleNpc, MidlanderFemale, MidlanderMaleNpc, MidlanderMale },
|
||||
[LalafellFemaleNpc] = new[]{ LalafellFemaleNpc, LalafellFemale, LalafellMaleNpc, LalafellMale, MidlanderMaleNpc, MidlanderMale },
|
||||
[AuRaFemaleNpc] = new[]{ AuRaFemaleNpc, AuRaFemale, MidlanderFemaleNpc, MidlanderFemale, MidlanderMaleNpc, MidlanderMale },
|
||||
[HrothgarFemaleNpc] = new[]{ HrothgarFemaleNpc, HrothgarFemale, RoegadynFemaleNpc, RoegadynFemale, MidlanderFemaleNpc, MidlanderFemale, MidlanderMaleNpc, MidlanderMale },
|
||||
[VieraFemaleNpc] = new[]{ VieraFemaleNpc, VieraFemale, MidlanderFemaleNpc, MidlanderFemale, MidlanderMaleNpc, MidlanderMale },
|
||||
[UnknownMaleNpc] = new[]{ UnknownMaleNpc, MidlanderMaleNpc, MidlanderMale },
|
||||
[UnknownFemaleNpc] = new[]{ UnknownFemaleNpc, MidlanderFemaleNpc, MidlanderFemale, MidlanderMaleNpc, MidlanderMale },
|
||||
// @formatter:on
|
||||
};
|
||||
}
|
||||
|
||||
public static partial class Names
|
||||
{
|
||||
public static GenderRace GenderRaceFromCode(string code)
|
||||
{
|
||||
return code switch
|
||||
{
|
||||
"0101" => MidlanderMale,
|
||||
"0104" => MidlanderMaleNpc,
|
||||
"0201" => MidlanderFemale,
|
||||
"0204" => MidlanderFemaleNpc,
|
||||
"0301" => HighlanderMale,
|
||||
"0304" => HighlanderMaleNpc,
|
||||
"0401" => HighlanderFemale,
|
||||
"0404" => HighlanderFemaleNpc,
|
||||
"0501" => ElezenMale,
|
||||
"0504" => ElezenMaleNpc,
|
||||
"0601" => ElezenFemale,
|
||||
"0604" => ElezenFemaleNpc,
|
||||
"0701" => MiqoteMale,
|
||||
"0704" => MiqoteMaleNpc,
|
||||
"0801" => MiqoteFemale,
|
||||
"0804" => MiqoteFemaleNpc,
|
||||
"0901" => RoegadynMale,
|
||||
"0904" => RoegadynMaleNpc,
|
||||
"1001" => RoegadynFemale,
|
||||
"1004" => RoegadynFemaleNpc,
|
||||
"1101" => LalafellMale,
|
||||
"1104" => LalafellMaleNpc,
|
||||
"1201" => LalafellFemale,
|
||||
"1204" => LalafellFemaleNpc,
|
||||
"1301" => AuRaMale,
|
||||
"1304" => AuRaMaleNpc,
|
||||
"1401" => AuRaFemale,
|
||||
"1404" => AuRaFemaleNpc,
|
||||
"1501" => HrothgarMale,
|
||||
"1504" => HrothgarMaleNpc,
|
||||
"1601" => HrothgarFemale,
|
||||
"1604" => HrothgarFemaleNpc,
|
||||
"1701" => VieraMale,
|
||||
"1704" => VieraMaleNpc,
|
||||
"1801" => VieraFemale,
|
||||
"1804" => VieraFemaleNpc,
|
||||
"9104" => UnknownMaleNpc,
|
||||
"9204" => UnknownFemaleNpc,
|
||||
_ => Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
public static GenderRace GenderRaceFromByte(byte value)
|
||||
{
|
||||
var gender = (Gender)(value & 0b111);
|
||||
var race = (ModelRace)(value >> 3);
|
||||
return CombinedRace(gender, race);
|
||||
}
|
||||
|
||||
public static GenderRace CombinedRace(Gender gender, ModelRace modelRace)
|
||||
{
|
||||
return gender switch
|
||||
{
|
||||
Gender.Male => modelRace switch
|
||||
{
|
||||
ModelRace.Midlander => MidlanderMale,
|
||||
ModelRace.Highlander => HighlanderMale,
|
||||
ModelRace.Elezen => ElezenMale,
|
||||
ModelRace.Lalafell => LalafellMale,
|
||||
ModelRace.Miqote => MiqoteMale,
|
||||
ModelRace.Roegadyn => RoegadynMale,
|
||||
ModelRace.AuRa => AuRaMale,
|
||||
ModelRace.Hrothgar => HrothgarMale,
|
||||
ModelRace.Viera => VieraMale,
|
||||
_ => Unknown,
|
||||
},
|
||||
Gender.MaleNpc => modelRace switch
|
||||
{
|
||||
ModelRace.Midlander => MidlanderMaleNpc,
|
||||
ModelRace.Highlander => HighlanderMaleNpc,
|
||||
ModelRace.Elezen => ElezenMaleNpc,
|
||||
ModelRace.Lalafell => LalafellMaleNpc,
|
||||
ModelRace.Miqote => MiqoteMaleNpc,
|
||||
ModelRace.Roegadyn => RoegadynMaleNpc,
|
||||
ModelRace.AuRa => AuRaMaleNpc,
|
||||
ModelRace.Hrothgar => HrothgarMaleNpc,
|
||||
ModelRace.Viera => VieraMaleNpc,
|
||||
_ => Unknown,
|
||||
},
|
||||
Gender.Female => modelRace switch
|
||||
{
|
||||
ModelRace.Midlander => MidlanderFemale,
|
||||
ModelRace.Highlander => HighlanderFemale,
|
||||
ModelRace.Elezen => ElezenFemale,
|
||||
ModelRace.Lalafell => LalafellFemale,
|
||||
ModelRace.Miqote => MiqoteFemale,
|
||||
ModelRace.Roegadyn => RoegadynFemale,
|
||||
ModelRace.AuRa => AuRaFemale,
|
||||
ModelRace.Hrothgar => HrothgarFemale,
|
||||
ModelRace.Viera => VieraFemale,
|
||||
_ => Unknown,
|
||||
},
|
||||
Gender.FemaleNpc => modelRace switch
|
||||
{
|
||||
ModelRace.Midlander => MidlanderFemaleNpc,
|
||||
ModelRace.Highlander => HighlanderFemaleNpc,
|
||||
ModelRace.Elezen => ElezenFemaleNpc,
|
||||
ModelRace.Lalafell => LalafellFemaleNpc,
|
||||
ModelRace.Miqote => MiqoteFemaleNpc,
|
||||
ModelRace.Roegadyn => RoegadynFemaleNpc,
|
||||
ModelRace.AuRa => AuRaFemaleNpc,
|
||||
ModelRace.Hrothgar => HrothgarFemaleNpc,
|
||||
ModelRace.Viera => VieraFemaleNpc,
|
||||
_ => Unknown,
|
||||
},
|
||||
_ => Unknown,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,319 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Functions;
|
||||
|
||||
namespace Penumbra.GameData.Enums;
|
||||
|
||||
public enum ResourceType : uint
|
||||
{
|
||||
Unknown = 0,
|
||||
Aet = 0x00616574,
|
||||
Amb = 0x00616D62,
|
||||
Atch = 0x61746368,
|
||||
Atex = 0x61746578,
|
||||
Avfx = 0x61766678,
|
||||
Awt = 0x00617774,
|
||||
Cmp = 0x00636D70,
|
||||
Dic = 0x00646963,
|
||||
Eid = 0x00656964,
|
||||
Envb = 0x656E7662,
|
||||
Eqdp = 0x65716470,
|
||||
Eqp = 0x00657170,
|
||||
Essb = 0x65737362,
|
||||
Est = 0x00657374,
|
||||
Evp = 0x00657670,
|
||||
Exd = 0x00657864,
|
||||
Exh = 0x00657868,
|
||||
Exl = 0x0065786C,
|
||||
Fdt = 0x00666474,
|
||||
Gfd = 0x00676664,
|
||||
Ggd = 0x00676764,
|
||||
Gmp = 0x00676D70,
|
||||
Gzd = 0x00677A64,
|
||||
Imc = 0x00696D63,
|
||||
Lcb = 0x006C6362,
|
||||
Lgb = 0x006C6762,
|
||||
Luab = 0x6C756162,
|
||||
Lvb = 0x006C7662,
|
||||
Mdl = 0x006D646C,
|
||||
Mlt = 0x006D6C74,
|
||||
Mtrl = 0x6D74726C,
|
||||
Obsb = 0x6F627362,
|
||||
Pap = 0x00706170,
|
||||
Pbd = 0x00706264,
|
||||
Pcb = 0x00706362,
|
||||
Phyb = 0x70687962,
|
||||
Plt = 0x00706C74,
|
||||
Scd = 0x00736364,
|
||||
Sgb = 0x00736762,
|
||||
Shcd = 0x73686364,
|
||||
Shpk = 0x7368706B,
|
||||
Sklb = 0x736B6C62,
|
||||
Skp = 0x00736B70,
|
||||
Stm = 0x0073746D,
|
||||
Svb = 0x00737662,
|
||||
Tera = 0x74657261,
|
||||
Tex = 0x00746578,
|
||||
Tmb = 0x00746D62,
|
||||
Ugd = 0x00756764,
|
||||
Uld = 0x00756C64,
|
||||
Waoe = 0x77616F65,
|
||||
Wtd = 0x00777464,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ResourceTypeFlag : ulong
|
||||
{
|
||||
Aet = 0x0000_0000_0000_0001,
|
||||
Amb = 0x0000_0000_0000_0002,
|
||||
Atch = 0x0000_0000_0000_0004,
|
||||
Atex = 0x0000_0000_0000_0008,
|
||||
Avfx = 0x0000_0000_0000_0010,
|
||||
Awt = 0x0000_0000_0000_0020,
|
||||
Cmp = 0x0000_0000_0000_0040,
|
||||
Dic = 0x0000_0000_0000_0080,
|
||||
Eid = 0x0000_0000_0000_0100,
|
||||
Envb = 0x0000_0000_0000_0200,
|
||||
Eqdp = 0x0000_0000_0000_0400,
|
||||
Eqp = 0x0000_0000_0000_0800,
|
||||
Essb = 0x0000_0000_0000_1000,
|
||||
Est = 0x0000_0000_0000_2000,
|
||||
Evp = 0x0000_0000_0000_4000,
|
||||
Exd = 0x0000_0000_0000_8000,
|
||||
Exh = 0x0000_0000_0001_0000,
|
||||
Exl = 0x0000_0000_0002_0000,
|
||||
Fdt = 0x0000_0000_0004_0000,
|
||||
Gfd = 0x0000_0000_0008_0000,
|
||||
Ggd = 0x0000_0000_0010_0000,
|
||||
Gmp = 0x0000_0000_0020_0000,
|
||||
Gzd = 0x0000_0000_0040_0000,
|
||||
Imc = 0x0000_0000_0080_0000,
|
||||
Lcb = 0x0000_0000_0100_0000,
|
||||
Lgb = 0x0000_0000_0200_0000,
|
||||
Luab = 0x0000_0000_0400_0000,
|
||||
Lvb = 0x0000_0000_0800_0000,
|
||||
Mdl = 0x0000_0000_1000_0000,
|
||||
Mlt = 0x0000_0000_2000_0000,
|
||||
Mtrl = 0x0000_0000_4000_0000,
|
||||
Obsb = 0x0000_0000_8000_0000,
|
||||
Pap = 0x0000_0001_0000_0000,
|
||||
Pbd = 0x0000_0002_0000_0000,
|
||||
Pcb = 0x0000_0004_0000_0000,
|
||||
Phyb = 0x0000_0008_0000_0000,
|
||||
Plt = 0x0000_0010_0000_0000,
|
||||
Scd = 0x0000_0020_0000_0000,
|
||||
Sgb = 0x0000_0040_0000_0000,
|
||||
Shcd = 0x0000_0080_0000_0000,
|
||||
Shpk = 0x0000_0100_0000_0000,
|
||||
Sklb = 0x0000_0200_0000_0000,
|
||||
Skp = 0x0000_0400_0000_0000,
|
||||
Stm = 0x0000_0800_0000_0000,
|
||||
Svb = 0x0000_1000_0000_0000,
|
||||
Tera = 0x0000_2000_0000_0000,
|
||||
Tex = 0x0000_4000_0000_0000,
|
||||
Tmb = 0x0000_8000_0000_0000,
|
||||
Ugd = 0x0001_0000_0000_0000,
|
||||
Uld = 0x0002_0000_0000_0000,
|
||||
Waoe = 0x0004_0000_0000_0000,
|
||||
Wtd = 0x0008_0000_0000_0000,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ResourceCategoryFlag : ushort
|
||||
{
|
||||
Common = 0x0001,
|
||||
BgCommon = 0x0002,
|
||||
Bg = 0x0004,
|
||||
Cut = 0x0008,
|
||||
Chara = 0x0010,
|
||||
Shader = 0x0020,
|
||||
Ui = 0x0040,
|
||||
Sound = 0x0080,
|
||||
Vfx = 0x0100,
|
||||
UiScript = 0x0200,
|
||||
Exd = 0x0400,
|
||||
GameScript = 0x0800,
|
||||
Music = 0x1000,
|
||||
SqpackTest = 0x2000,
|
||||
}
|
||||
|
||||
public static class ResourceExtensions
|
||||
{
|
||||
public static readonly ResourceTypeFlag AllResourceTypes = Enum.GetValues<ResourceTypeFlag>().Aggregate((v, f) => v | f);
|
||||
public static readonly ResourceCategoryFlag AllResourceCategories = Enum.GetValues<ResourceCategoryFlag>().Aggregate((v, f) => v | f);
|
||||
|
||||
public static ResourceTypeFlag ToFlag(this ResourceType type)
|
||||
=> type switch
|
||||
{
|
||||
ResourceType.Aet => ResourceTypeFlag.Aet,
|
||||
ResourceType.Amb => ResourceTypeFlag.Amb,
|
||||
ResourceType.Atch => ResourceTypeFlag.Atch,
|
||||
ResourceType.Atex => ResourceTypeFlag.Atex,
|
||||
ResourceType.Avfx => ResourceTypeFlag.Avfx,
|
||||
ResourceType.Awt => ResourceTypeFlag.Awt,
|
||||
ResourceType.Cmp => ResourceTypeFlag.Cmp,
|
||||
ResourceType.Dic => ResourceTypeFlag.Dic,
|
||||
ResourceType.Eid => ResourceTypeFlag.Eid,
|
||||
ResourceType.Envb => ResourceTypeFlag.Envb,
|
||||
ResourceType.Eqdp => ResourceTypeFlag.Eqdp,
|
||||
ResourceType.Eqp => ResourceTypeFlag.Eqp,
|
||||
ResourceType.Essb => ResourceTypeFlag.Essb,
|
||||
ResourceType.Est => ResourceTypeFlag.Est,
|
||||
ResourceType.Evp => ResourceTypeFlag.Evp,
|
||||
ResourceType.Exd => ResourceTypeFlag.Exd,
|
||||
ResourceType.Exh => ResourceTypeFlag.Exh,
|
||||
ResourceType.Exl => ResourceTypeFlag.Exl,
|
||||
ResourceType.Fdt => ResourceTypeFlag.Fdt,
|
||||
ResourceType.Gfd => ResourceTypeFlag.Gfd,
|
||||
ResourceType.Ggd => ResourceTypeFlag.Ggd,
|
||||
ResourceType.Gmp => ResourceTypeFlag.Gmp,
|
||||
ResourceType.Gzd => ResourceTypeFlag.Gzd,
|
||||
ResourceType.Imc => ResourceTypeFlag.Imc,
|
||||
ResourceType.Lcb => ResourceTypeFlag.Lcb,
|
||||
ResourceType.Lgb => ResourceTypeFlag.Lgb,
|
||||
ResourceType.Luab => ResourceTypeFlag.Luab,
|
||||
ResourceType.Lvb => ResourceTypeFlag.Lvb,
|
||||
ResourceType.Mdl => ResourceTypeFlag.Mdl,
|
||||
ResourceType.Mlt => ResourceTypeFlag.Mlt,
|
||||
ResourceType.Mtrl => ResourceTypeFlag.Mtrl,
|
||||
ResourceType.Obsb => ResourceTypeFlag.Obsb,
|
||||
ResourceType.Pap => ResourceTypeFlag.Pap,
|
||||
ResourceType.Pbd => ResourceTypeFlag.Pbd,
|
||||
ResourceType.Pcb => ResourceTypeFlag.Pcb,
|
||||
ResourceType.Phyb => ResourceTypeFlag.Phyb,
|
||||
ResourceType.Plt => ResourceTypeFlag.Plt,
|
||||
ResourceType.Scd => ResourceTypeFlag.Scd,
|
||||
ResourceType.Sgb => ResourceTypeFlag.Sgb,
|
||||
ResourceType.Shcd => ResourceTypeFlag.Shcd,
|
||||
ResourceType.Shpk => ResourceTypeFlag.Shpk,
|
||||
ResourceType.Sklb => ResourceTypeFlag.Sklb,
|
||||
ResourceType.Skp => ResourceTypeFlag.Skp,
|
||||
ResourceType.Stm => ResourceTypeFlag.Stm,
|
||||
ResourceType.Svb => ResourceTypeFlag.Svb,
|
||||
ResourceType.Tera => ResourceTypeFlag.Tera,
|
||||
ResourceType.Tex => ResourceTypeFlag.Tex,
|
||||
ResourceType.Tmb => ResourceTypeFlag.Tmb,
|
||||
ResourceType.Ugd => ResourceTypeFlag.Ugd,
|
||||
ResourceType.Uld => ResourceTypeFlag.Uld,
|
||||
ResourceType.Waoe => ResourceTypeFlag.Waoe,
|
||||
ResourceType.Wtd => ResourceTypeFlag.Wtd,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public static bool FitsFlag(this ResourceType type, ResourceTypeFlag flags)
|
||||
=> (type.ToFlag() & flags) != 0;
|
||||
|
||||
public static ResourceCategoryFlag ToFlag(this ResourceCategory type)
|
||||
=> type switch
|
||||
{
|
||||
ResourceCategory.Common => ResourceCategoryFlag.Common,
|
||||
ResourceCategory.BgCommon => ResourceCategoryFlag.BgCommon,
|
||||
ResourceCategory.Bg => ResourceCategoryFlag.Bg,
|
||||
ResourceCategory.Cut => ResourceCategoryFlag.Cut,
|
||||
ResourceCategory.Chara => ResourceCategoryFlag.Chara,
|
||||
ResourceCategory.Shader => ResourceCategoryFlag.Shader,
|
||||
ResourceCategory.Ui => ResourceCategoryFlag.Ui,
|
||||
ResourceCategory.Sound => ResourceCategoryFlag.Sound,
|
||||
ResourceCategory.Vfx => ResourceCategoryFlag.Vfx,
|
||||
ResourceCategory.UiScript => ResourceCategoryFlag.UiScript,
|
||||
ResourceCategory.Exd => ResourceCategoryFlag.Exd,
|
||||
ResourceCategory.GameScript => ResourceCategoryFlag.GameScript,
|
||||
ResourceCategory.Music => ResourceCategoryFlag.Music,
|
||||
ResourceCategory.SqpackTest => ResourceCategoryFlag.SqpackTest,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public static bool FitsFlag(this ResourceCategory type, ResourceCategoryFlag flags)
|
||||
=> (type.ToFlag() & flags) != 0;
|
||||
|
||||
public static ResourceType FromBytes(byte a1, byte a2, byte a3)
|
||||
=> (ResourceType)(((uint)ByteStringFunctions.AsciiToLower(a1) << 16)
|
||||
| ((uint)ByteStringFunctions.AsciiToLower(a2) << 8)
|
||||
| ByteStringFunctions.AsciiToLower(a3));
|
||||
|
||||
public static ResourceType FromBytes(byte a1, byte a2, byte a3, byte a4)
|
||||
=> (ResourceType)(((uint)ByteStringFunctions.AsciiToLower(a1) << 24)
|
||||
| ((uint)ByteStringFunctions.AsciiToLower(a2) << 16)
|
||||
| ((uint)ByteStringFunctions.AsciiToLower(a3) << 8)
|
||||
| ByteStringFunctions.AsciiToLower(a4));
|
||||
|
||||
public static ResourceType FromBytes(char a1, char a2, char a3)
|
||||
=> FromBytes((byte)a1, (byte)a2, (byte)a3);
|
||||
|
||||
public static ResourceType FromBytes(char a1, char a2, char a3, char a4)
|
||||
=> FromBytes((byte)a1, (byte)a2, (byte)a3, (byte)a4);
|
||||
|
||||
public static ResourceType Type(string path)
|
||||
{
|
||||
var ext = Path.GetExtension(path.AsSpan());
|
||||
ext = ext.Length == 0 ? path.AsSpan() : ext[1..];
|
||||
|
||||
return ext.Length switch
|
||||
{
|
||||
0 => 0,
|
||||
1 => (ResourceType)ext[^1],
|
||||
2 => FromBytes('\0', ext[^2], ext[^1]),
|
||||
3 => FromBytes(ext[^3], ext[^2], ext[^1]),
|
||||
_ => FromBytes(ext[^4], ext[^3], ext[^2], ext[^1]),
|
||||
};
|
||||
}
|
||||
|
||||
public static ResourceType Type(ByteString path)
|
||||
{
|
||||
var extIdx = path.LastIndexOf((byte)'.');
|
||||
var ext = extIdx == -1 ? path : extIdx == path.Length - 1 ? ByteString.Empty : path.Substring(extIdx + 1);
|
||||
|
||||
return ext.Length switch
|
||||
{
|
||||
0 => 0,
|
||||
1 => (ResourceType)ext[^1],
|
||||
2 => FromBytes(0, ext[^2], ext[^1]),
|
||||
3 => FromBytes(ext[^3], ext[^2], ext[^1]),
|
||||
_ => FromBytes(ext[^4], ext[^3], ext[^2], ext[^1]),
|
||||
};
|
||||
}
|
||||
|
||||
public static ResourceCategory Category(ByteString path)
|
||||
{
|
||||
if (path.Length < 3)
|
||||
return ResourceCategory.Debug;
|
||||
|
||||
return ByteStringFunctions.AsciiToUpper(path[0]) switch
|
||||
{
|
||||
(byte)'C' => ByteStringFunctions.AsciiToUpper(path[1]) switch
|
||||
{
|
||||
(byte)'O' => ResourceCategory.Common,
|
||||
(byte)'U' => ResourceCategory.Cut,
|
||||
(byte)'H' => ResourceCategory.Chara,
|
||||
_ => ResourceCategory.Debug,
|
||||
},
|
||||
(byte)'B' => ByteStringFunctions.AsciiToUpper(path[2]) switch
|
||||
{
|
||||
(byte)'C' => ResourceCategory.BgCommon,
|
||||
(byte)'/' => ResourceCategory.Bg,
|
||||
_ => ResourceCategory.Debug,
|
||||
},
|
||||
(byte)'S' => ByteStringFunctions.AsciiToUpper(path[1]) switch
|
||||
{
|
||||
(byte)'H' => ResourceCategory.Shader,
|
||||
(byte)'O' => ResourceCategory.Sound,
|
||||
(byte)'Q' => ResourceCategory.SqpackTest,
|
||||
_ => ResourceCategory.Debug,
|
||||
},
|
||||
(byte)'U' => ByteStringFunctions.AsciiToUpper(path[2]) switch
|
||||
{
|
||||
(byte)'/' => ResourceCategory.Ui,
|
||||
(byte)'S' => ResourceCategory.UiScript,
|
||||
_ => ResourceCategory.Debug,
|
||||
},
|
||||
(byte)'V' => ResourceCategory.Vfx,
|
||||
(byte)'E' => ResourceCategory.Exd,
|
||||
(byte)'G' => ResourceCategory.GameScript,
|
||||
(byte)'M' => ResourceCategory.Music,
|
||||
_ => ResourceCategory.Debug,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace Penumbra.GameData.Enums;
|
||||
|
||||
public enum RspAttribute : byte
|
||||
{
|
||||
MaleMinSize,
|
||||
MaleMaxSize,
|
||||
MaleMinTail,
|
||||
MaleMaxTail,
|
||||
FemaleMinSize,
|
||||
FemaleMaxSize,
|
||||
FemaleMinTail,
|
||||
FemaleMaxTail,
|
||||
BustMinX,
|
||||
BustMinY,
|
||||
BustMinZ,
|
||||
BustMaxX,
|
||||
BustMaxY,
|
||||
BustMaxZ,
|
||||
NumAttributes,
|
||||
}
|
||||
|
||||
public static class RspAttributeExtensions
|
||||
{
|
||||
public static Gender ToGender( this RspAttribute attribute )
|
||||
{
|
||||
return attribute switch
|
||||
{
|
||||
RspAttribute.MaleMinSize => Gender.Male,
|
||||
RspAttribute.MaleMaxSize => Gender.Male,
|
||||
RspAttribute.MaleMinTail => Gender.Male,
|
||||
RspAttribute.MaleMaxTail => Gender.Male,
|
||||
RspAttribute.FemaleMinSize => Gender.Female,
|
||||
RspAttribute.FemaleMaxSize => Gender.Female,
|
||||
RspAttribute.FemaleMinTail => Gender.Female,
|
||||
RspAttribute.FemaleMaxTail => Gender.Female,
|
||||
RspAttribute.BustMinX => Gender.Female,
|
||||
RspAttribute.BustMinY => Gender.Female,
|
||||
RspAttribute.BustMinZ => Gender.Female,
|
||||
RspAttribute.BustMaxX => Gender.Female,
|
||||
RspAttribute.BustMaxY => Gender.Female,
|
||||
RspAttribute.BustMaxZ => Gender.Female,
|
||||
_ => Gender.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToUngenderedString( this RspAttribute attribute )
|
||||
{
|
||||
return attribute switch
|
||||
{
|
||||
RspAttribute.MaleMinSize => "MinSize",
|
||||
RspAttribute.MaleMaxSize => "MaxSize",
|
||||
RspAttribute.MaleMinTail => "MinTail",
|
||||
RspAttribute.MaleMaxTail => "MaxTail",
|
||||
RspAttribute.FemaleMinSize => "MinSize",
|
||||
RspAttribute.FemaleMaxSize => "MaxSize",
|
||||
RspAttribute.FemaleMinTail => "MinTail",
|
||||
RspAttribute.FemaleMaxTail => "MaxTail",
|
||||
RspAttribute.BustMinX => "BustMinX",
|
||||
RspAttribute.BustMinY => "BustMinY",
|
||||
RspAttribute.BustMinZ => "BustMinZ",
|
||||
RspAttribute.BustMaxX => "BustMaxX",
|
||||
RspAttribute.BustMaxY => "BustMaxY",
|
||||
RspAttribute.BustMaxZ => "BustMaxZ",
|
||||
_ => "",
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToFullString( this RspAttribute attribute )
|
||||
{
|
||||
return attribute switch
|
||||
{
|
||||
RspAttribute.MaleMinSize => "Male Minimum Size",
|
||||
RspAttribute.MaleMaxSize => "Male Maximum Size",
|
||||
RspAttribute.FemaleMinSize => "Female Minimum Size",
|
||||
RspAttribute.FemaleMaxSize => "Female Maximum Size",
|
||||
RspAttribute.BustMinX => "Bust Minimum X-Axis",
|
||||
RspAttribute.BustMaxX => "Bust Maximum X-Axis",
|
||||
RspAttribute.BustMinY => "Bust Minimum Y-Axis",
|
||||
RspAttribute.BustMaxY => "Bust Maximum Y-Axis",
|
||||
RspAttribute.BustMinZ => "Bust Minimum Z-Axis",
|
||||
RspAttribute.BustMaxZ => "Bust Maximum Z-Axis",
|
||||
RspAttribute.MaleMinTail => "Male Minimum Tail Length",
|
||||
RspAttribute.MaleMaxTail => "Male Maximum Tail Length",
|
||||
RspAttribute.FemaleMinTail => "Female Minimum Tail Length",
|
||||
RspAttribute.FemaleMaxTail => "Female Maximum Tail Length",
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
namespace Penumbra.GameData.Enums;
|
||||
|
||||
public enum WeaponCategory : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
Pugilist,
|
||||
Gladiator,
|
||||
Marauder,
|
||||
Archer,
|
||||
Lancer,
|
||||
Thaumaturge1,
|
||||
Thaumaturge2,
|
||||
Conjurer1,
|
||||
Conjurer2,
|
||||
Arcanist,
|
||||
Shield,
|
||||
CarpenterMain,
|
||||
CarpenterOff,
|
||||
BlacksmithMain,
|
||||
BlacksmithOff,
|
||||
ArmorerMain,
|
||||
ArmorerOff,
|
||||
GoldsmithMain,
|
||||
GoldsmithOff,
|
||||
LeatherworkerMain,
|
||||
LeatherworkerOff,
|
||||
WeaverMain,
|
||||
WeaverOff,
|
||||
AlchemistMain,
|
||||
AlchemistOff,
|
||||
CulinarianMain,
|
||||
CulinarianOff,
|
||||
MinerMain,
|
||||
MinerOff,
|
||||
BotanistMain,
|
||||
BotanistOff,
|
||||
FisherMain,
|
||||
Rogue = 84,
|
||||
DarkKnight = 87,
|
||||
Machinist = 88,
|
||||
Astrologian = 89,
|
||||
Samurai = 96,
|
||||
RedMage = 97,
|
||||
Scholar = 98,
|
||||
FisherOff = 99,
|
||||
BlueMage = 105,
|
||||
Gunbreaker = 106,
|
||||
Dancer = 107,
|
||||
Reaper = 108,
|
||||
Sage = 109,
|
||||
}
|
||||
|
|
@ -1,283 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public class AvfxFile : IWritable
|
||||
{
|
||||
public struct Block
|
||||
{
|
||||
public uint Name;
|
||||
public uint Size;
|
||||
public byte[] Data;
|
||||
|
||||
public Block(BinaryReader r)
|
||||
{
|
||||
Name = r.ReadUInt32();
|
||||
Size = r.ReadUInt32();
|
||||
Data = r.ReadBytes((int)Size.RoundTo4());
|
||||
}
|
||||
|
||||
public byte ToBool()
|
||||
=> BitConverter.ToBoolean(Data) ? (byte)1 : (byte)0;
|
||||
|
||||
public uint ToUint()
|
||||
=> BitConverter.ToUInt32(Data);
|
||||
|
||||
public float ToFloat()
|
||||
=> BitConverter.ToSingle(Data);
|
||||
|
||||
public new string ToString()
|
||||
{
|
||||
var span = Data.AsSpan(0, (int)Size - 1);
|
||||
return Encoding.UTF8.GetString(span);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly Vector3 BadVector = new(float.NaN);
|
||||
|
||||
public Vector3 ClipBox = BadVector;
|
||||
public Vector3 ClipBoxSize = BadVector;
|
||||
public Vector3 RevisedValuesPos = BadVector;
|
||||
public Vector3 RevisedValuesRot = BadVector;
|
||||
public Vector3 RevisedValuesScale = BadVector;
|
||||
public Vector3 RevisedValuesColor = BadVector;
|
||||
|
||||
public uint Version = uint.MaxValue;
|
||||
public uint DrawLayerType = uint.MaxValue;
|
||||
public uint DrawOrderType = uint.MaxValue;
|
||||
public uint DirectionalLightSourceType = uint.MaxValue;
|
||||
public uint PointLightsType1 = uint.MaxValue;
|
||||
public uint PointLightsType2 = uint.MaxValue;
|
||||
|
||||
public float BiasZmaxScale = float.NaN;
|
||||
public float BiasZmaxDistance = float.NaN;
|
||||
public float NearClipBegin = float.NaN;
|
||||
public float NearClipEnd = float.NaN;
|
||||
public float FadeInnerX = float.NaN;
|
||||
public float FadeOuterX = float.NaN;
|
||||
public float FadeInnerY = float.NaN;
|
||||
public float FadeOuterY = float.NaN;
|
||||
public float FadeInnerZ = float.NaN;
|
||||
public float FadeOuterZ = float.NaN;
|
||||
public float FarClipBegin = float.NaN;
|
||||
public float FarClipEnd = float.NaN;
|
||||
public float SoftParticleFadeRange = float.NaN;
|
||||
public float SoftKeyOffset = float.NaN;
|
||||
public float GlobalFogInfluence = float.NaN;
|
||||
|
||||
public byte IsDelayFastParticle = byte.MaxValue;
|
||||
public byte IsFitGround = byte.MaxValue;
|
||||
public byte IsTransformSkip = byte.MaxValue;
|
||||
public byte IsAllStopOnHide = byte.MaxValue;
|
||||
public byte CanBeClippedOut = byte.MaxValue;
|
||||
public byte ClipBoxEnabled = byte.MaxValue;
|
||||
public byte IsCameraSpace = byte.MaxValue;
|
||||
public byte IsFullEnvLight = byte.MaxValue;
|
||||
public byte IsClipOwnSetting = byte.MaxValue;
|
||||
public byte FadeEnabledX = byte.MaxValue;
|
||||
public byte FadeEnabledY = byte.MaxValue;
|
||||
public byte FadeEnabledZ = byte.MaxValue;
|
||||
public byte GlobalFogEnabled = byte.MaxValue;
|
||||
public byte LtsEnabled = byte.MaxValue;
|
||||
|
||||
public Block[] Schedulers = Array.Empty<Block>();
|
||||
public Block[] Timelines = Array.Empty<Block>();
|
||||
public Block[] Emitters = Array.Empty<Block>();
|
||||
public Block[] Particles = Array.Empty<Block>();
|
||||
public Block[] Effectors = Array.Empty<Block>();
|
||||
public Block[] Binders = Array.Empty<Block>();
|
||||
public string[] Textures = Array.Empty<string>();
|
||||
public Block[] Models = Array.Empty<Block>();
|
||||
|
||||
public bool Valid
|
||||
=> true;
|
||||
|
||||
public AvfxFile(byte[] data)
|
||||
{
|
||||
using var stream = new MemoryStream(data);
|
||||
using var r = new BinaryReader(stream);
|
||||
|
||||
var name = r.ReadUInt32();
|
||||
var size = r.ReadUInt32();
|
||||
var schedulerCount = 0;
|
||||
var timelineCount = 0;
|
||||
var emitterCount = 0;
|
||||
var particleCount = 0;
|
||||
var effectorCount = 0;
|
||||
var binderCount = 0;
|
||||
var textureCount = 0;
|
||||
var modelCount = 0;
|
||||
while (r.BaseStream.Position < size)
|
||||
{
|
||||
var block = new Block(r);
|
||||
switch (block.Name)
|
||||
{
|
||||
// @formatter:off
|
||||
case AvfxMagic.Version: Version = block.ToUint(); break;
|
||||
case AvfxMagic.IsDelayFastParticle: IsDelayFastParticle = block.ToBool(); break;
|
||||
case AvfxMagic.IsFitGround: IsFitGround = block.ToBool(); break;
|
||||
case AvfxMagic.IsTransformSkip: IsTransformSkip = block.ToBool(); break;
|
||||
case AvfxMagic.IsAllStopOnHide: IsAllStopOnHide = block.ToBool(); break;
|
||||
case AvfxMagic.CanBeClippedOut: CanBeClippedOut = block.ToBool(); break;
|
||||
case AvfxMagic.ClipBoxEnabled: ClipBoxEnabled = block.ToBool(); break;
|
||||
case AvfxMagic.ClipBoxX: ClipBox.X = block.ToFloat(); break;
|
||||
case AvfxMagic.ClipBoxY: ClipBox.Y = block.ToFloat(); break;
|
||||
case AvfxMagic.ClipBoxZ: ClipBox.Z = block.ToFloat(); break;
|
||||
case AvfxMagic.ClipBoxSizeX: ClipBoxSize.X = block.ToFloat(); break;
|
||||
case AvfxMagic.ClipBoxSizeY: ClipBoxSize.Y = block.ToFloat(); break;
|
||||
case AvfxMagic.ClipBoxSizeZ: ClipBoxSize.Z = block.ToFloat(); break;
|
||||
case AvfxMagic.BiasZmaxScale: BiasZmaxScale = block.ToFloat(); break;
|
||||
case AvfxMagic.BiasZmaxDistance: BiasZmaxDistance = block.ToFloat(); break;
|
||||
case AvfxMagic.IsCameraSpace: IsCameraSpace = block.ToBool(); break;
|
||||
case AvfxMagic.IsFullEnvLight: IsFullEnvLight = block.ToBool(); break;
|
||||
case AvfxMagic.IsClipOwnSetting: IsClipOwnSetting = block.ToBool(); break;
|
||||
case AvfxMagic.NearClipBegin: NearClipBegin = block.ToFloat(); break;
|
||||
case AvfxMagic.NearClipEnd: NearClipEnd = block.ToFloat(); break;
|
||||
case AvfxMagic.FarClipBegin: FarClipBegin = block.ToFloat(); break;
|
||||
case AvfxMagic.FarClipEnd: FarClipEnd = block.ToFloat(); break;
|
||||
case AvfxMagic.SoftParticleFadeRange: SoftParticleFadeRange = block.ToFloat(); break;
|
||||
case AvfxMagic.SoftKeyOffset: SoftKeyOffset = block.ToFloat(); break;
|
||||
case AvfxMagic.DrawLayerType: DrawLayerType = block.ToUint(); break;
|
||||
case AvfxMagic.DrawOrderType: DrawOrderType = block.ToUint(); break;
|
||||
case AvfxMagic.DirectionalLightSourceType: DirectionalLightSourceType = block.ToUint(); break;
|
||||
case AvfxMagic.PointLightsType1: PointLightsType1 = block.ToUint(); break;
|
||||
case AvfxMagic.PointLightsType2: PointLightsType2 = block.ToUint(); break;
|
||||
case AvfxMagic.RevisedValuesPosX: RevisedValuesPos.X = block.ToFloat(); break;
|
||||
case AvfxMagic.RevisedValuesPosY: RevisedValuesPos.Y = block.ToFloat(); break;
|
||||
case AvfxMagic.RevisedValuesPosZ: RevisedValuesPos.Z = block.ToFloat(); break;
|
||||
case AvfxMagic.RevisedValuesRotX: RevisedValuesRot.X = block.ToFloat(); break;
|
||||
case AvfxMagic.RevisedValuesRotY: RevisedValuesRot.Y = block.ToFloat(); break;
|
||||
case AvfxMagic.RevisedValuesRotZ: RevisedValuesRot.Z = block.ToFloat(); break;
|
||||
case AvfxMagic.RevisedValuesScaleX: RevisedValuesScale.X = block.ToFloat(); break;
|
||||
case AvfxMagic.RevisedValuesScaleY: RevisedValuesScale.Y = block.ToFloat(); break;
|
||||
case AvfxMagic.RevisedValuesScaleZ: RevisedValuesScale.Z = block.ToFloat(); break;
|
||||
case AvfxMagic.RevisedValuesColorR: RevisedValuesColor.X = block.ToFloat(); break;
|
||||
case AvfxMagic.RevisedValuesColorG: RevisedValuesColor.Y = block.ToFloat(); break;
|
||||
case AvfxMagic.RevisedValuesColorB: RevisedValuesColor.Z = block.ToFloat(); break;
|
||||
case AvfxMagic.FadeEnabledX: FadeEnabledX = block.ToBool(); break;
|
||||
case AvfxMagic.FadeInnerX: FadeInnerX = block.ToFloat(); break;
|
||||
case AvfxMagic.FadeOuterX: FadeOuterX = block.ToFloat(); break;
|
||||
case AvfxMagic.FadeEnabledY: FadeEnabledY = block.ToBool(); break;
|
||||
case AvfxMagic.FadeInnerY: FadeInnerY = block.ToFloat(); break;
|
||||
case AvfxMagic.FadeOuterY: FadeOuterY = block.ToFloat(); break;
|
||||
case AvfxMagic.FadeEnabledZ: FadeEnabledZ = block.ToBool(); break;
|
||||
case AvfxMagic.FadeInnerZ: FadeInnerZ = block.ToFloat(); break;
|
||||
case AvfxMagic.FadeOuterZ: FadeOuterZ = block.ToFloat(); break;
|
||||
case AvfxMagic.GlobalFogEnabled: GlobalFogEnabled = block.ToBool(); break;
|
||||
case AvfxMagic.GlobalFogInfluence: GlobalFogInfluence = block.ToFloat(); break;
|
||||
case AvfxMagic.LtsEnabled: LtsEnabled = block.ToBool(); break;
|
||||
case AvfxMagic.NumSchedulers: Schedulers = new Block[block.ToUint()]; break;
|
||||
case AvfxMagic.NumTimelines: Timelines = new Block[block.ToUint()]; break;
|
||||
case AvfxMagic.NumEmitters: Emitters = new Block[block.ToUint()]; break;
|
||||
case AvfxMagic.NumParticles: Particles = new Block[block.ToUint()]; break;
|
||||
case AvfxMagic.NumEffectors: Effectors = new Block[block.ToUint()]; break;
|
||||
case AvfxMagic.NumBinders: Binders = new Block[block.ToUint()]; break;
|
||||
case AvfxMagic.NumTextures: Textures = new string[block.ToUint()]; break;
|
||||
case AvfxMagic.NumModels: Models = new Block[block.ToUint()]; break;
|
||||
case AvfxMagic.Scheduler: Schedulers[schedulerCount++] = block; break;
|
||||
case AvfxMagic.Timeline: Timelines[timelineCount++] = block; break;
|
||||
case AvfxMagic.Emitter: Emitters[emitterCount++] = block; break;
|
||||
case AvfxMagic.Particle: Particles[particleCount++] = block; break;
|
||||
case AvfxMagic.Effector: Effectors[effectorCount++] = block; break;
|
||||
case AvfxMagic.Binder: Binders[binderCount++] = block; break;
|
||||
case AvfxMagic.Texture: Textures[textureCount++] = block.ToString(); break;
|
||||
case AvfxMagic.Model: Models[modelCount++] = block; break;
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public byte[] Write()
|
||||
{
|
||||
using var m = new MemoryStream(512 * 1024);
|
||||
using var w = new BinaryWriter(m);
|
||||
|
||||
w.Write(AvfxMagic.AvfxBase);
|
||||
var sizePos = w.BaseStream.Position;
|
||||
w.Write(0u);
|
||||
w.WriteBlock(AvfxMagic.Version, Version)
|
||||
.WriteBlock(AvfxMagic.IsDelayFastParticle, IsDelayFastParticle)
|
||||
.WriteBlock(AvfxMagic.IsFitGround, IsFitGround)
|
||||
.WriteBlock(AvfxMagic.IsTransformSkip, IsTransformSkip)
|
||||
.WriteBlock(AvfxMagic.IsAllStopOnHide, IsAllStopOnHide)
|
||||
.WriteBlock(AvfxMagic.CanBeClippedOut, CanBeClippedOut)
|
||||
.WriteBlock(AvfxMagic.ClipBoxEnabled, ClipBoxEnabled)
|
||||
.WriteBlock(AvfxMagic.ClipBoxX, ClipBox.X)
|
||||
.WriteBlock(AvfxMagic.ClipBoxY, ClipBox.Y)
|
||||
.WriteBlock(AvfxMagic.ClipBoxZ, ClipBox.Z)
|
||||
.WriteBlock(AvfxMagic.ClipBoxSizeX, ClipBoxSize.X)
|
||||
.WriteBlock(AvfxMagic.ClipBoxSizeY, ClipBoxSize.Y)
|
||||
.WriteBlock(AvfxMagic.ClipBoxSizeZ, ClipBoxSize.Z)
|
||||
.WriteBlock(AvfxMagic.BiasZmaxScale, BiasZmaxScale)
|
||||
.WriteBlock(AvfxMagic.BiasZmaxDistance, BiasZmaxDistance)
|
||||
.WriteBlock(AvfxMagic.IsCameraSpace, IsCameraSpace)
|
||||
.WriteBlock(AvfxMagic.IsFullEnvLight, IsFullEnvLight)
|
||||
.WriteBlock(AvfxMagic.IsClipOwnSetting, IsClipOwnSetting)
|
||||
.WriteBlock(AvfxMagic.NearClipBegin, NearClipBegin)
|
||||
.WriteBlock(AvfxMagic.NearClipEnd, NearClipEnd)
|
||||
.WriteBlock(AvfxMagic.FarClipBegin, FarClipBegin)
|
||||
.WriteBlock(AvfxMagic.FarClipEnd, FarClipEnd)
|
||||
.WriteBlock(AvfxMagic.SoftParticleFadeRange, SoftParticleFadeRange)
|
||||
.WriteBlock(AvfxMagic.SoftKeyOffset, SoftKeyOffset)
|
||||
.WriteBlock(AvfxMagic.DrawLayerType, DrawLayerType)
|
||||
.WriteBlock(AvfxMagic.DrawOrderType, DrawOrderType)
|
||||
.WriteBlock(AvfxMagic.DirectionalLightSourceType, DirectionalLightSourceType)
|
||||
.WriteBlock(AvfxMagic.PointLightsType1, PointLightsType1)
|
||||
.WriteBlock(AvfxMagic.PointLightsType2, PointLightsType2)
|
||||
.WriteBlock(AvfxMagic.RevisedValuesPosX, RevisedValuesPos.X)
|
||||
.WriteBlock(AvfxMagic.RevisedValuesPosY, RevisedValuesPos.Y)
|
||||
.WriteBlock(AvfxMagic.RevisedValuesPosZ, RevisedValuesPos.Z)
|
||||
.WriteBlock(AvfxMagic.RevisedValuesRotX, RevisedValuesRot.X)
|
||||
.WriteBlock(AvfxMagic.RevisedValuesRotY, RevisedValuesRot.Y)
|
||||
.WriteBlock(AvfxMagic.RevisedValuesRotZ, RevisedValuesRot.Z)
|
||||
.WriteBlock(AvfxMagic.RevisedValuesScaleX, RevisedValuesScale.X)
|
||||
.WriteBlock(AvfxMagic.RevisedValuesScaleY, RevisedValuesScale.Y)
|
||||
.WriteBlock(AvfxMagic.RevisedValuesScaleZ, RevisedValuesScale.Z)
|
||||
.WriteBlock(AvfxMagic.RevisedValuesColorR, RevisedValuesColor.X)
|
||||
.WriteBlock(AvfxMagic.RevisedValuesColorG, RevisedValuesColor.Y)
|
||||
.WriteBlock(AvfxMagic.RevisedValuesColorB, RevisedValuesColor.Z)
|
||||
.WriteBlock(AvfxMagic.FadeEnabledX, FadeEnabledX)
|
||||
.WriteBlock(AvfxMagic.FadeInnerX, FadeInnerX)
|
||||
.WriteBlock(AvfxMagic.FadeOuterX, FadeOuterX)
|
||||
.WriteBlock(AvfxMagic.FadeEnabledY, FadeEnabledY)
|
||||
.WriteBlock(AvfxMagic.FadeInnerY, FadeInnerY)
|
||||
.WriteBlock(AvfxMagic.FadeOuterY, FadeOuterY)
|
||||
.WriteBlock(AvfxMagic.FadeEnabledZ, FadeEnabledZ)
|
||||
.WriteBlock(AvfxMagic.FadeInnerZ, FadeInnerZ)
|
||||
.WriteBlock(AvfxMagic.FadeOuterZ, FadeOuterZ)
|
||||
.WriteBlock(AvfxMagic.GlobalFogEnabled, GlobalFogEnabled)
|
||||
.WriteBlock(AvfxMagic.GlobalFogInfluence, GlobalFogInfluence)
|
||||
.WriteBlock(AvfxMagic.LtsEnabled, LtsEnabled)
|
||||
.WriteBlock(AvfxMagic.NumSchedulers, (uint)Schedulers.Length)
|
||||
.WriteBlock(AvfxMagic.NumTimelines, (uint)Timelines.Length)
|
||||
.WriteBlock(AvfxMagic.NumEmitters, (uint)Emitters.Length)
|
||||
.WriteBlock(AvfxMagic.NumParticles, (uint)Particles.Length)
|
||||
.WriteBlock(AvfxMagic.NumEffectors, (uint)Effectors.Length)
|
||||
.WriteBlock(AvfxMagic.NumBinders, (uint)Binders.Length)
|
||||
.WriteBlock(AvfxMagic.NumTextures, (uint)Textures.Length)
|
||||
.WriteBlock(AvfxMagic.NumModels, (uint)Models.Length);
|
||||
foreach (var block in Schedulers)
|
||||
w.WriteBlock(block);
|
||||
foreach (var block in Timelines)
|
||||
w.WriteBlock(block);
|
||||
foreach (var block in Emitters)
|
||||
w.WriteBlock(block);
|
||||
foreach (var block in Particles)
|
||||
w.WriteBlock(block);
|
||||
foreach (var block in Effectors)
|
||||
w.WriteBlock(block);
|
||||
foreach (var block in Binders)
|
||||
w.WriteBlock(block);
|
||||
foreach (var texture in Textures)
|
||||
w.WriteTextureBlock(texture);
|
||||
foreach (var block in Models)
|
||||
w.WriteBlock(block);
|
||||
w.Seek((int)sizePos, SeekOrigin.Begin);
|
||||
w.Write((uint)w.BaseStream.Length - 8u);
|
||||
return m.ToArray();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
// ReSharper disable ShiftExpressionZeroLeftOperand
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public static class AvfxMagic
|
||||
{
|
||||
public const uint AvfxBase = ('A' << 24) | ('V' << 16) | ('F' << 8) | (uint)'X';
|
||||
public const uint Version = (000 << 24) | ('V' << 16) | ('e' << 8) | (uint)'r';
|
||||
public const uint IsDelayFastParticle = ('b' << 24) | ('D' << 16) | ('F' << 8) | (uint)'P';
|
||||
public const uint IsFitGround = (000 << 24) | ('b' << 16) | ('F' << 8) | (uint)'G';
|
||||
public const uint IsTransformSkip = (000 << 24) | ('b' << 16) | ('T' << 8) | (uint)'S';
|
||||
public const uint IsAllStopOnHide = ('b' << 24) | ('A' << 16) | ('S' << 8) | (uint)'H';
|
||||
public const uint CanBeClippedOut = ('b' << 24) | ('C' << 16) | ('B' << 8) | (uint)'C';
|
||||
public const uint ClipBoxEnabled = ('b' << 24) | ('C' << 16) | ('u' << 8) | (uint)'l';
|
||||
public const uint ClipBoxX = ('C' << 24) | ('B' << 16) | ('P' << 8) | (uint)'x';
|
||||
public const uint ClipBoxY = ('C' << 24) | ('B' << 16) | ('P' << 8) | (uint)'y';
|
||||
public const uint ClipBoxZ = ('C' << 24) | ('B' << 16) | ('P' << 8) | (uint)'z';
|
||||
public const uint ClipBoxSizeX = ('C' << 24) | ('B' << 16) | ('S' << 8) | (uint)'x';
|
||||
public const uint ClipBoxSizeY = ('C' << 24) | ('B' << 16) | ('S' << 8) | (uint)'y';
|
||||
public const uint ClipBoxSizeZ = ('C' << 24) | ('B' << 16) | ('S' << 8) | (uint)'z';
|
||||
public const uint BiasZmaxScale = ('Z' << 24) | ('B' << 16) | ('M' << 8) | (uint)'s';
|
||||
public const uint BiasZmaxDistance = ('Z' << 24) | ('B' << 16) | ('M' << 8) | (uint)'d';
|
||||
public const uint IsCameraSpace = ('b' << 24) | ('C' << 16) | ('m' << 8) | (uint)'S';
|
||||
public const uint IsFullEnvLight = ('b' << 24) | ('F' << 16) | ('E' << 8) | (uint)'L';
|
||||
public const uint IsClipOwnSetting = ('b' << 24) | ('O' << 16) | ('S' << 8) | (uint)'t';
|
||||
public const uint NearClipBegin = (000 << 24) | ('N' << 16) | ('C' << 8) | (uint)'B';
|
||||
public const uint NearClipEnd = (000 << 24) | ('N' << 16) | ('C' << 8) | (uint)'E';
|
||||
public const uint FarClipBegin = (000 << 24) | ('F' << 16) | ('C' << 8) | (uint)'B';
|
||||
public const uint FarClipEnd = (000 << 24) | ('F' << 16) | ('C' << 8) | (uint)'E';
|
||||
public const uint SoftParticleFadeRange = ('S' << 24) | ('P' << 16) | ('F' << 8) | (uint)'R';
|
||||
public const uint SoftKeyOffset = (000 << 24) | ('S' << 16) | ('K' << 8) | (uint)'O';
|
||||
public const uint DrawLayerType = ('D' << 24) | ('w' << 16) | ('L' << 8) | (uint)'y';
|
||||
public const uint DrawOrderType = ('D' << 24) | ('w' << 16) | ('O' << 8) | (uint)'T';
|
||||
public const uint DirectionalLightSourceType = ('D' << 24) | ('L' << 16) | ('S' << 8) | (uint)'T';
|
||||
public const uint PointLightsType1 = ('P' << 24) | ('L' << 16) | ('1' << 8) | (uint)'S';
|
||||
public const uint PointLightsType2 = ('P' << 24) | ('L' << 16) | ('2' << 8) | (uint)'S';
|
||||
public const uint RevisedValuesPosX = ('R' << 24) | ('v' << 16) | ('P' << 8) | (uint)'x';
|
||||
public const uint RevisedValuesPosY = ('R' << 24) | ('v' << 16) | ('P' << 8) | (uint)'y';
|
||||
public const uint RevisedValuesPosZ = ('R' << 24) | ('v' << 16) | ('P' << 8) | (uint)'z';
|
||||
public const uint RevisedValuesRotX = ('R' << 24) | ('v' << 16) | ('R' << 8) | (uint)'x';
|
||||
public const uint RevisedValuesRotY = ('R' << 24) | ('v' << 16) | ('R' << 8) | (uint)'y';
|
||||
public const uint RevisedValuesRotZ = ('R' << 24) | ('v' << 16) | ('R' << 8) | (uint)'z';
|
||||
public const uint RevisedValuesScaleX = ('R' << 24) | ('v' << 16) | ('S' << 8) | (uint)'x';
|
||||
public const uint RevisedValuesScaleY = ('R' << 24) | ('v' << 16) | ('S' << 8) | (uint)'y';
|
||||
public const uint RevisedValuesScaleZ = ('R' << 24) | ('v' << 16) | ('S' << 8) | (uint)'z';
|
||||
public const uint RevisedValuesColorR = (000 << 24) | ('R' << 16) | ('v' << 8) | (uint)'R';
|
||||
public const uint RevisedValuesColorG = (000 << 24) | ('R' << 16) | ('v' << 8) | (uint)'G';
|
||||
public const uint RevisedValuesColorB = (000 << 24) | ('R' << 16) | ('v' << 8) | (uint)'B';
|
||||
public const uint FadeEnabledX = ('A' << 24) | ('F' << 16) | ('X' << 8) | (uint)'e';
|
||||
public const uint FadeInnerX = ('A' << 24) | ('F' << 16) | ('X' << 8) | (uint)'i';
|
||||
public const uint FadeOuterX = ('A' << 24) | ('F' << 16) | ('X' << 8) | (uint)'o';
|
||||
public const uint FadeEnabledY = ('A' << 24) | ('F' << 16) | ('Y' << 8) | (uint)'e';
|
||||
public const uint FadeInnerY = ('A' << 24) | ('F' << 16) | ('Y' << 8) | (uint)'i';
|
||||
public const uint FadeOuterY = ('A' << 24) | ('F' << 16) | ('Y' << 8) | (uint)'o';
|
||||
public const uint FadeEnabledZ = ('A' << 24) | ('F' << 16) | ('Z' << 8) | (uint)'e';
|
||||
public const uint FadeInnerZ = ('A' << 24) | ('F' << 16) | ('Z' << 8) | (uint)'i';
|
||||
public const uint FadeOuterZ = ('A' << 24) | ('F' << 16) | ('Z' << 8) | (uint)'o';
|
||||
public const uint GlobalFogEnabled = ('b' << 24) | ('G' << 16) | ('F' << 8) | (uint)'E';
|
||||
public const uint GlobalFogInfluence = ('G' << 24) | ('F' << 16) | ('I' << 8) | (uint)'M';
|
||||
public const uint LtsEnabled = ('b' << 24) | ('L' << 16) | ('T' << 8) | (uint)'S';
|
||||
public const uint NumSchedulers = ('S' << 24) | ('c' << 16) | ('C' << 8) | (uint)'n';
|
||||
public const uint NumTimelines = ('T' << 24) | ('l' << 16) | ('C' << 8) | (uint)'n';
|
||||
public const uint NumEmitters = ('E' << 24) | ('m' << 16) | ('C' << 8) | (uint)'n';
|
||||
public const uint NumParticles = ('P' << 24) | ('r' << 16) | ('C' << 8) | (uint)'n';
|
||||
public const uint NumEffectors = ('E' << 24) | ('f' << 16) | ('C' << 8) | (uint)'n';
|
||||
public const uint NumBinders = ('B' << 24) | ('d' << 16) | ('C' << 8) | (uint)'n';
|
||||
public const uint NumTextures = ('T' << 24) | ('x' << 16) | ('C' << 8) | (uint)'n';
|
||||
public const uint NumModels = ('M' << 24) | ('d' << 16) | ('C' << 8) | (uint)'n';
|
||||
public const uint Scheduler = ('S' << 24) | ('c' << 16) | ('h' << 8) | (uint)'d';
|
||||
public const uint Timeline = ('T' << 24) | ('m' << 16) | ('L' << 8) | (uint)'n';
|
||||
public const uint Emitter = ('E' << 24) | ('m' << 16) | ('i' << 8) | (uint)'t';
|
||||
public const uint Particle = ('P' << 24) | ('t' << 16) | ('c' << 8) | (uint)'l';
|
||||
public const uint Effector = ('E' << 24) | ('f' << 16) | ('c' << 8) | (uint)'t';
|
||||
public const uint Binder = ('B' << 24) | ('i' << 16) | ('n' << 8) | (uint)'d';
|
||||
public const uint Texture = (000 << 24) | ('T' << 16) | ('e' << 8) | (uint)'x';
|
||||
public const uint Model = ('M' << 24) | ('o' << 16) | ('d' << 8) | (uint)'l';
|
||||
|
||||
internal static uint RoundTo4(this uint size)
|
||||
{
|
||||
var rest = size & 0b11u;
|
||||
return rest > 0 ? (size & ~0b11u) + 4u : size;
|
||||
}
|
||||
|
||||
internal static BinaryWriter WriteTextureBlock(this BinaryWriter bw, string texture)
|
||||
{
|
||||
bw.Write(Texture);
|
||||
var bytes = Encoding.UTF8.GetBytes(texture);
|
||||
var size = (uint)bytes.Length + 1u;
|
||||
bw.Write(size);
|
||||
bw.Write(bytes);
|
||||
bw.Write((byte)0);
|
||||
for (var end = size.RoundTo4(); size < end; ++size)
|
||||
bw.Write((byte)0);
|
||||
return bw;
|
||||
}
|
||||
|
||||
internal static BinaryWriter WriteBlock(this BinaryWriter bw, AvfxFile.Block block)
|
||||
{
|
||||
bw.Write(block.Name);
|
||||
bw.Write(block.Size);
|
||||
bw.Write(block.Data);
|
||||
return bw;
|
||||
}
|
||||
|
||||
internal static BinaryWriter WriteBlock(this BinaryWriter bw, uint magic, uint value)
|
||||
{
|
||||
if (value != uint.MaxValue)
|
||||
{
|
||||
bw.Write(magic);
|
||||
bw.Write(4u);
|
||||
bw.Write(value);
|
||||
}
|
||||
|
||||
return bw;
|
||||
}
|
||||
|
||||
internal static BinaryWriter WriteBlock(this BinaryWriter bw, uint magic, byte value)
|
||||
{
|
||||
if (value != byte.MaxValue)
|
||||
{
|
||||
bw.Write(magic);
|
||||
bw.Write(4u);
|
||||
bw.Write(value == 1 ? 1u : 0u);
|
||||
}
|
||||
|
||||
return bw;
|
||||
}
|
||||
|
||||
internal static BinaryWriter WriteBlock(this BinaryWriter bw, uint magic, float value)
|
||||
{
|
||||
if (!float.IsNaN(value))
|
||||
{
|
||||
bw.Write(magic);
|
||||
bw.Write(4u);
|
||||
bw.Write(value);
|
||||
}
|
||||
|
||||
return bw;
|
||||
}
|
||||
|
||||
internal static BinaryWriter WriteBlock(this BinaryWriter bw, uint magicX, uint magicY, uint magicZ, Vector3 value)
|
||||
=> bw.WriteBlock(magicX, value.X)
|
||||
.WriteBlock(magicY, value.Y)
|
||||
.WriteBlock(magicZ, value.Z);
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public interface IWritable
|
||||
{
|
||||
public bool Valid { get; }
|
||||
public byte[] Write();
|
||||
}
|
||||
|
|
@ -1,285 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Lumina.Data.Parsing;
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public partial class MdlFile
|
||||
{
|
||||
private static uint Write(BinaryWriter w, string s, long basePos)
|
||||
{
|
||||
var currentPos = w.BaseStream.Position;
|
||||
w.Write(Encoding.UTF8.GetBytes(s));
|
||||
w.Write((byte)0);
|
||||
return (uint)(currentPos - basePos);
|
||||
}
|
||||
|
||||
private List<uint> WriteStrings(BinaryWriter w)
|
||||
{
|
||||
var startPos = (int)w.BaseStream.Position;
|
||||
var basePos = startPos + 8;
|
||||
var count = (ushort)(Attributes.Length + Bones.Length + Materials.Length + Shapes.Length);
|
||||
|
||||
w.Write(count);
|
||||
w.Seek(basePos, SeekOrigin.Begin);
|
||||
var ret = Attributes.Concat(Bones)
|
||||
.Concat(Materials)
|
||||
.Concat(Shapes.Select(s => s.ShapeName))
|
||||
.Select(attribute => Write(w, attribute, basePos)).ToList();
|
||||
|
||||
var padding = (w.BaseStream.Position & 0b111) > 0 ? (w.BaseStream.Position & ~0b111) + 8 : w.BaseStream.Position;
|
||||
for (var i = w.BaseStream.Position; i < padding; ++i)
|
||||
w.Write((byte)0);
|
||||
var size = (int)w.BaseStream.Position - basePos;
|
||||
w.Seek(startPos + 4, SeekOrigin.Begin);
|
||||
w.Write((uint)size);
|
||||
w.Seek(basePos + size, SeekOrigin.Begin);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void WriteModelFileHeader(BinaryWriter w, uint runtimeSize)
|
||||
{
|
||||
w.Write(Version);
|
||||
w.Write(StackSize);
|
||||
w.Write(runtimeSize);
|
||||
w.Write((ushort)VertexDeclarations.Length);
|
||||
w.Write((ushort)Materials.Length);
|
||||
w.Write(VertexOffset[0] > 0 ? VertexOffset[0] + runtimeSize : 0u);
|
||||
w.Write(VertexOffset[1] > 0 ? VertexOffset[1] + runtimeSize : 0u);
|
||||
w.Write(VertexOffset[2] > 0 ? VertexOffset[2] + runtimeSize : 0u);
|
||||
w.Write(IndexOffset[0] > 0 ? IndexOffset[0] + runtimeSize : 0u);
|
||||
w.Write(IndexOffset[1] > 0 ? IndexOffset[1] + runtimeSize : 0u);
|
||||
w.Write(IndexOffset[2] > 0 ? IndexOffset[2] + runtimeSize : 0u);
|
||||
w.Write(VertexBufferSize[0]);
|
||||
w.Write(VertexBufferSize[1]);
|
||||
w.Write(VertexBufferSize[2]);
|
||||
w.Write(IndexBufferSize[0]);
|
||||
w.Write(IndexBufferSize[1]);
|
||||
w.Write(IndexBufferSize[2]);
|
||||
w.Write(LodCount);
|
||||
w.Write(EnableIndexBufferStreaming);
|
||||
w.Write(EnableEdgeGeometry);
|
||||
w.Write((byte)0); // Padding
|
||||
}
|
||||
|
||||
private void WriteModelHeader(BinaryWriter w)
|
||||
{
|
||||
w.Write(Radius);
|
||||
w.Write((ushort)Meshes.Length);
|
||||
w.Write((ushort)Attributes.Length);
|
||||
w.Write((ushort)SubMeshes.Length);
|
||||
w.Write((ushort)Materials.Length);
|
||||
w.Write((ushort)Bones.Length);
|
||||
w.Write((ushort)BoneTables.Length);
|
||||
w.Write((ushort)Shapes.Length);
|
||||
w.Write((ushort)ShapeMeshes.Length);
|
||||
w.Write((ushort)ShapeValues.Length);
|
||||
w.Write(LodCount);
|
||||
w.Write((byte)Flags1);
|
||||
w.Write((ushort)ElementIds.Length);
|
||||
w.Write((byte)TerrainShadowMeshes.Length);
|
||||
w.Write((byte)Flags2);
|
||||
w.Write(ModelClipOutDistance);
|
||||
w.Write(ShadowClipOutDistance);
|
||||
w.Write(Unknown4);
|
||||
w.Write((ushort)TerrainShadowSubMeshes.Length);
|
||||
w.Write(Unknown5);
|
||||
w.Write(BgChangeMaterialIndex);
|
||||
w.Write(BgCrestChangeMaterialIndex);
|
||||
w.Write(Unknown6);
|
||||
w.Write(Unknown7);
|
||||
w.Write(Unknown8);
|
||||
w.Write(Unknown9);
|
||||
w.Write((uint)0); // 6 byte padding
|
||||
w.Write((ushort)0);
|
||||
}
|
||||
|
||||
|
||||
private static void Write(BinaryWriter w, in MdlStructs.VertexElement vertex)
|
||||
{
|
||||
w.Write(vertex.Stream);
|
||||
w.Write(vertex.Offset);
|
||||
w.Write(vertex.Type);
|
||||
w.Write(vertex.Usage);
|
||||
w.Write(vertex.UsageIndex);
|
||||
w.Write((ushort)0); // 3 byte padding
|
||||
w.Write((byte)0);
|
||||
}
|
||||
|
||||
private static void Write(BinaryWriter w, in MdlStructs.VertexDeclarationStruct vertexDecl)
|
||||
{
|
||||
foreach (var vertex in vertexDecl.VertexElements)
|
||||
Write(w, vertex);
|
||||
|
||||
Write(w, new MdlStructs.VertexElement() { Stream = 255 });
|
||||
w.Seek((int)(NumVertices - 1 - vertexDecl.VertexElements.Length) * 8, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
private static void Write(BinaryWriter w, in MdlStructs.ElementIdStruct elementId)
|
||||
{
|
||||
w.Write(elementId.ElementId);
|
||||
w.Write(elementId.ParentBoneName);
|
||||
w.Write(elementId.Translate[0]);
|
||||
w.Write(elementId.Translate[1]);
|
||||
w.Write(elementId.Translate[2]);
|
||||
w.Write(elementId.Rotate[0]);
|
||||
w.Write(elementId.Rotate[1]);
|
||||
w.Write(elementId.Rotate[2]);
|
||||
}
|
||||
|
||||
private static unsafe void Write<T>(BinaryWriter w, in T data) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &data)
|
||||
{
|
||||
var bytePtr = (byte*)ptr;
|
||||
var size = sizeof(T);
|
||||
var span = new ReadOnlySpan<byte>(bytePtr, size);
|
||||
w.Write(span);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Write(BinaryWriter w, MdlStructs.MeshStruct mesh)
|
||||
{
|
||||
w.Write(mesh.VertexCount);
|
||||
w.Write((ushort)0); // padding
|
||||
w.Write(mesh.IndexCount);
|
||||
w.Write(mesh.MaterialIndex);
|
||||
w.Write(mesh.SubMeshIndex);
|
||||
w.Write(mesh.SubMeshCount);
|
||||
w.Write(mesh.BoneTableIndex);
|
||||
w.Write(mesh.StartIndex);
|
||||
w.Write(mesh.VertexBufferOffset[0]);
|
||||
w.Write(mesh.VertexBufferOffset[1]);
|
||||
w.Write(mesh.VertexBufferOffset[2]);
|
||||
w.Write(mesh.VertexBufferStride[0]);
|
||||
w.Write(mesh.VertexBufferStride[1]);
|
||||
w.Write(mesh.VertexBufferStride[2]);
|
||||
w.Write(mesh.VertexStreamCount);
|
||||
}
|
||||
|
||||
private static void Write(BinaryWriter w, MdlStructs.BoneTableStruct bone)
|
||||
{
|
||||
foreach (var index in bone.BoneIndex)
|
||||
w.Write(index);
|
||||
|
||||
w.Write(bone.BoneCount);
|
||||
w.Write((ushort)0); // 3 bytes padding
|
||||
w.Write((byte)0);
|
||||
}
|
||||
|
||||
private void Write(BinaryWriter w, int shapeIdx, IReadOnlyList<uint> offsets)
|
||||
{
|
||||
var shape = Shapes[shapeIdx];
|
||||
var offset = offsets[Attributes.Length + Bones.Length + Materials.Length + shapeIdx];
|
||||
w.Write(offset);
|
||||
w.Write(shape.ShapeMeshStartIndex[0]);
|
||||
w.Write(shape.ShapeMeshStartIndex[1]);
|
||||
w.Write(shape.ShapeMeshStartIndex[2]);
|
||||
w.Write(shape.ShapeMeshCount[0]);
|
||||
w.Write(shape.ShapeMeshCount[1]);
|
||||
w.Write(shape.ShapeMeshCount[2]);
|
||||
}
|
||||
|
||||
private static void Write(BinaryWriter w, MdlStructs.BoundingBoxStruct box)
|
||||
{
|
||||
w.Write(box.Min[0]);
|
||||
w.Write(box.Min[1]);
|
||||
w.Write(box.Min[2]);
|
||||
w.Write(box.Min[3]);
|
||||
w.Write(box.Max[0]);
|
||||
w.Write(box.Max[1]);
|
||||
w.Write(box.Max[2]);
|
||||
w.Write(box.Max[3]);
|
||||
}
|
||||
|
||||
public byte[] Write()
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
using (var w = new BinaryWriter(stream))
|
||||
{
|
||||
// Skip and write this later when we actually know it.
|
||||
w.Seek((int)FileHeaderSize, SeekOrigin.Begin);
|
||||
|
||||
foreach (var vertexDecl in VertexDeclarations)
|
||||
Write(w, vertexDecl);
|
||||
|
||||
var offsets = WriteStrings(w);
|
||||
WriteModelHeader(w);
|
||||
|
||||
foreach (var elementId in ElementIds)
|
||||
Write(w, elementId);
|
||||
|
||||
foreach (var lod in Lods)
|
||||
Write(w, lod);
|
||||
|
||||
if (Flags2.HasFlag(MdlStructs.ModelFlags2.ExtraLodEnabled))
|
||||
foreach (var extraLod in ExtraLods)
|
||||
Write(w, extraLod);
|
||||
|
||||
foreach (var mesh in Meshes)
|
||||
Write(w, mesh);
|
||||
|
||||
for (var i = 0; i < Attributes.Length; ++i)
|
||||
w.Write(offsets[i]);
|
||||
|
||||
foreach (var terrainShadowMesh in TerrainShadowMeshes)
|
||||
Write(w, terrainShadowMesh);
|
||||
|
||||
foreach (var subMesh in SubMeshes)
|
||||
Write(w, subMesh);
|
||||
|
||||
foreach (var terrainShadowSubMesh in TerrainShadowSubMeshes)
|
||||
Write(w, terrainShadowSubMesh);
|
||||
|
||||
for (var i = 0; i < Materials.Length; ++i)
|
||||
w.Write(offsets[Attributes.Length + Bones.Length + i]);
|
||||
|
||||
for (var i = 0; i < Bones.Length; ++i)
|
||||
w.Write(offsets[Attributes.Length + i]);
|
||||
|
||||
foreach (var boneTable in BoneTables)
|
||||
Write(w, boneTable);
|
||||
|
||||
for (var i = 0; i < Shapes.Length; ++i)
|
||||
Write(w, i, offsets);
|
||||
|
||||
foreach (var shapeMesh in ShapeMeshes)
|
||||
Write(w, shapeMesh);
|
||||
|
||||
foreach (var shapeValue in ShapeValues)
|
||||
Write(w, shapeValue);
|
||||
|
||||
w.Write(SubMeshBoneMap.Length * 2);
|
||||
foreach (var bone in SubMeshBoneMap)
|
||||
w.Write(bone);
|
||||
|
||||
var pos = w.BaseStream.Position + 1;
|
||||
var padding = (byte) (pos & 0b111);
|
||||
if (padding > 0)
|
||||
padding = (byte) (8 - padding);
|
||||
w.Write(padding);
|
||||
for (var i = 0; i < padding; ++i)
|
||||
w.Write((byte) (0xDEADBEEFF00DCAFEu >> (8 * (7 - i))));
|
||||
|
||||
Write(w, BoundingBoxes);
|
||||
Write(w, ModelBoundingBoxes);
|
||||
Write(w, WaterBoundingBoxes);
|
||||
Write(w, VerticalFogBoundingBoxes);
|
||||
foreach (var box in BoneBoundingBoxes)
|
||||
Write(w, box);
|
||||
|
||||
var totalSize = w.BaseStream.Position;
|
||||
var runtimeSize = (uint)(totalSize - StackSize - FileHeaderSize);
|
||||
w.Write(RemainingData);
|
||||
|
||||
// Write header data.
|
||||
w.Seek(0, SeekOrigin.Begin);
|
||||
WriteModelFileHeader(w, runtimeSize);
|
||||
}
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Lumina.Data;
|
||||
using Lumina.Data.Parsing;
|
||||
using Lumina.Extensions;
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public partial class MdlFile : IWritable
|
||||
{
|
||||
public const uint NumVertices = 17;
|
||||
public const uint FileHeaderSize = 0x44;
|
||||
|
||||
// Refers to string, thus not Lumina struct.
|
||||
public struct Shape
|
||||
{
|
||||
public string ShapeName = string.Empty;
|
||||
public ushort[] ShapeMeshStartIndex;
|
||||
public ushort[] ShapeMeshCount;
|
||||
|
||||
public Shape(MdlStructs.ShapeStruct data, uint[] offsets, string[] strings)
|
||||
{
|
||||
var idx = offsets.AsSpan().IndexOf(data.StringOffset);
|
||||
ShapeName = idx >= 0 ? strings[idx] : string.Empty;
|
||||
ShapeMeshStartIndex = data.ShapeMeshStartIndex;
|
||||
ShapeMeshCount = data.ShapeMeshCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Raw data to write back.
|
||||
public uint Version;
|
||||
public float Radius;
|
||||
public float ModelClipOutDistance;
|
||||
public float ShadowClipOutDistance;
|
||||
public byte BgChangeMaterialIndex;
|
||||
public byte BgCrestChangeMaterialIndex;
|
||||
public ushort Unknown4;
|
||||
public byte Unknown5;
|
||||
public byte Unknown6;
|
||||
public ushort Unknown7;
|
||||
public ushort Unknown8;
|
||||
public ushort Unknown9;
|
||||
|
||||
// Offsets are stored relative to RuntimeSize instead of file start.
|
||||
public uint[] VertexOffset;
|
||||
public uint[] IndexOffset;
|
||||
|
||||
public uint[] VertexBufferSize;
|
||||
public uint[] IndexBufferSize;
|
||||
public byte LodCount;
|
||||
public bool EnableIndexBufferStreaming;
|
||||
public bool EnableEdgeGeometry;
|
||||
|
||||
|
||||
public MdlStructs.ModelFlags1 Flags1;
|
||||
public MdlStructs.ModelFlags2 Flags2;
|
||||
|
||||
public MdlStructs.BoundingBoxStruct BoundingBoxes;
|
||||
public MdlStructs.BoundingBoxStruct ModelBoundingBoxes;
|
||||
public MdlStructs.BoundingBoxStruct WaterBoundingBoxes;
|
||||
public MdlStructs.BoundingBoxStruct VerticalFogBoundingBoxes;
|
||||
|
||||
public MdlStructs.VertexDeclarationStruct[] VertexDeclarations;
|
||||
public MdlStructs.ElementIdStruct[] ElementIds;
|
||||
public MdlStructs.MeshStruct[] Meshes;
|
||||
public MdlStructs.BoneTableStruct[] BoneTables;
|
||||
public MdlStructs.BoundingBoxStruct[] BoneBoundingBoxes;
|
||||
public MdlStructs.SubmeshStruct[] SubMeshes;
|
||||
public MdlStructs.ShapeMeshStruct[] ShapeMeshes;
|
||||
public MdlStructs.ShapeValueStruct[] ShapeValues;
|
||||
public MdlStructs.TerrainShadowMeshStruct[] TerrainShadowMeshes;
|
||||
public MdlStructs.TerrainShadowSubmeshStruct[] TerrainShadowSubMeshes;
|
||||
public MdlStructs.LodStruct[] Lods;
|
||||
public MdlStructs.ExtraLodStruct[] ExtraLods;
|
||||
public ushort[] SubMeshBoneMap;
|
||||
|
||||
// Strings are written in order
|
||||
public string[] Attributes;
|
||||
public string[] Bones;
|
||||
public string[] Materials;
|
||||
public Shape[] Shapes;
|
||||
|
||||
// Raw, unparsed data.
|
||||
public byte[] RemainingData;
|
||||
|
||||
public bool Valid { get; }
|
||||
|
||||
public MdlFile(byte[] data)
|
||||
{
|
||||
using var stream = new MemoryStream(data);
|
||||
using var r = new LuminaBinaryReader(stream);
|
||||
|
||||
var header = LoadModelFileHeader(r);
|
||||
LodCount = header.LodCount;
|
||||
VertexBufferSize = header.VertexBufferSize;
|
||||
IndexBufferSize = header.IndexBufferSize;
|
||||
VertexOffset = header.VertexOffset;
|
||||
IndexOffset = header.IndexOffset;
|
||||
for (var i = 0; i < 3; ++i)
|
||||
{
|
||||
if (VertexOffset[i] > 0)
|
||||
VertexOffset[i] -= header.RuntimeSize;
|
||||
|
||||
if (IndexOffset[i] > 0)
|
||||
IndexOffset[i] -= header.RuntimeSize;
|
||||
}
|
||||
|
||||
VertexDeclarations = new MdlStructs.VertexDeclarationStruct[header.VertexDeclarationCount];
|
||||
for (var i = 0; i < header.VertexDeclarationCount; ++i)
|
||||
VertexDeclarations[i] = MdlStructs.VertexDeclarationStruct.Read(r);
|
||||
|
||||
var (offsets, strings) = LoadStrings(r);
|
||||
|
||||
var modelHeader = LoadModelHeader(r);
|
||||
ElementIds = new MdlStructs.ElementIdStruct[modelHeader.ElementIdCount];
|
||||
for (var i = 0; i < modelHeader.ElementIdCount; i++)
|
||||
ElementIds[i] = MdlStructs.ElementIdStruct.Read(r);
|
||||
|
||||
Lods = r.ReadStructuresAsArray<MdlStructs.LodStruct>(3);
|
||||
ExtraLods = modelHeader.ExtraLodEnabled
|
||||
? r.ReadStructuresAsArray<MdlStructs.ExtraLodStruct>(3)
|
||||
: Array.Empty<MdlStructs.ExtraLodStruct>();
|
||||
|
||||
Meshes = new MdlStructs.MeshStruct[modelHeader.MeshCount];
|
||||
for (var i = 0; i < modelHeader.MeshCount; i++)
|
||||
Meshes[i] = MdlStructs.MeshStruct.Read(r);
|
||||
|
||||
Attributes = new string[modelHeader.AttributeCount];
|
||||
for (var i = 0; i < modelHeader.AttributeCount; ++i)
|
||||
{
|
||||
var offset = r.ReadUInt32();
|
||||
var stringIdx = offsets.AsSpan().IndexOf(offset);
|
||||
Attributes[i] = stringIdx >= 0 ? strings[stringIdx] : string.Empty;
|
||||
}
|
||||
|
||||
TerrainShadowMeshes = r.ReadStructuresAsArray<MdlStructs.TerrainShadowMeshStruct>(modelHeader.TerrainShadowMeshCount);
|
||||
SubMeshes = r.ReadStructuresAsArray<MdlStructs.SubmeshStruct>(modelHeader.SubmeshCount);
|
||||
TerrainShadowSubMeshes = r.ReadStructuresAsArray<MdlStructs.TerrainShadowSubmeshStruct>(modelHeader.TerrainShadowSubmeshCount);
|
||||
|
||||
Materials = new string[modelHeader.MaterialCount];
|
||||
for (var i = 0; i < modelHeader.MaterialCount; ++i)
|
||||
{
|
||||
var offset = r.ReadUInt32();
|
||||
var stringIdx = offsets.AsSpan().IndexOf(offset);
|
||||
Materials[i] = stringIdx >= 0 ? strings[stringIdx] : string.Empty;
|
||||
}
|
||||
|
||||
Bones = new string[modelHeader.BoneCount];
|
||||
for (var i = 0; i < modelHeader.BoneCount; ++i)
|
||||
{
|
||||
var offset = r.ReadUInt32();
|
||||
var stringIdx = offsets.AsSpan().IndexOf(offset);
|
||||
Bones[i] = stringIdx >= 0 ? strings[stringIdx] : string.Empty;
|
||||
}
|
||||
|
||||
BoneTables = new MdlStructs.BoneTableStruct[modelHeader.BoneTableCount];
|
||||
for (var i = 0; i < modelHeader.BoneTableCount; i++)
|
||||
BoneTables[i] = MdlStructs.BoneTableStruct.Read(r);
|
||||
|
||||
Shapes = new Shape[modelHeader.ShapeCount];
|
||||
for (var i = 0; i < modelHeader.ShapeCount; i++)
|
||||
Shapes[i] = new Shape(MdlStructs.ShapeStruct.Read(r), offsets, strings);
|
||||
|
||||
ShapeMeshes = r.ReadStructuresAsArray<MdlStructs.ShapeMeshStruct>(modelHeader.ShapeMeshCount);
|
||||
ShapeValues = r.ReadStructuresAsArray<MdlStructs.ShapeValueStruct>(modelHeader.ShapeValueCount);
|
||||
|
||||
var submeshBoneMapSize = r.ReadUInt32();
|
||||
SubMeshBoneMap = r.ReadStructures<ushort>((int)submeshBoneMapSize / 2).ToArray();
|
||||
|
||||
var paddingAmount = r.ReadByte();
|
||||
r.Seek(r.BaseStream.Position + paddingAmount);
|
||||
|
||||
// Dunno what this first one is for?
|
||||
BoundingBoxes = MdlStructs.BoundingBoxStruct.Read(r);
|
||||
ModelBoundingBoxes = MdlStructs.BoundingBoxStruct.Read(r);
|
||||
WaterBoundingBoxes = MdlStructs.BoundingBoxStruct.Read(r);
|
||||
VerticalFogBoundingBoxes = MdlStructs.BoundingBoxStruct.Read(r);
|
||||
BoneBoundingBoxes = new MdlStructs.BoundingBoxStruct[modelHeader.BoneCount];
|
||||
for (var i = 0; i < modelHeader.BoneCount; i++)
|
||||
BoneBoundingBoxes[i] = MdlStructs.BoundingBoxStruct.Read(r);
|
||||
|
||||
var runtimePadding = header.RuntimeSize + FileHeaderSize + header.StackSize - r.BaseStream.Position;
|
||||
if (runtimePadding > 0)
|
||||
r.ReadBytes((int)runtimePadding);
|
||||
RemainingData = r.ReadBytes((int)(r.BaseStream.Length - r.BaseStream.Position));
|
||||
Valid = true;
|
||||
}
|
||||
|
||||
private MdlStructs.ModelFileHeader LoadModelFileHeader(LuminaBinaryReader r)
|
||||
{
|
||||
var header = MdlStructs.ModelFileHeader.Read(r);
|
||||
Version = header.Version;
|
||||
EnableIndexBufferStreaming = header.EnableIndexBufferStreaming;
|
||||
EnableEdgeGeometry = header.EnableEdgeGeometry;
|
||||
return header;
|
||||
}
|
||||
|
||||
private MdlStructs.ModelHeader LoadModelHeader(BinaryReader r)
|
||||
{
|
||||
var modelHeader = r.ReadStructure<MdlStructs.ModelHeader>();
|
||||
Radius = modelHeader.Radius;
|
||||
Flags1 = (MdlStructs.ModelFlags1)(modelHeader.GetType()
|
||||
.GetField("Flags1", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(modelHeader)
|
||||
?? 0);
|
||||
Flags2 = (MdlStructs.ModelFlags2)(modelHeader.GetType()
|
||||
.GetField("Flags2", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(modelHeader)
|
||||
?? 0);
|
||||
ModelClipOutDistance = modelHeader.ModelClipOutDistance;
|
||||
ShadowClipOutDistance = modelHeader.ShadowClipOutDistance;
|
||||
Unknown4 = modelHeader.Unknown4;
|
||||
Unknown5 = (byte)(modelHeader.GetType()
|
||||
.GetField("Unknown5", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(modelHeader)
|
||||
?? 0);
|
||||
Unknown6 = modelHeader.Unknown6;
|
||||
Unknown7 = modelHeader.Unknown7;
|
||||
Unknown8 = modelHeader.Unknown8;
|
||||
Unknown9 = modelHeader.Unknown9;
|
||||
BgChangeMaterialIndex = modelHeader.BGChangeMaterialIndex;
|
||||
BgCrestChangeMaterialIndex = modelHeader.BGCrestChangeMaterialIndex;
|
||||
|
||||
return modelHeader;
|
||||
}
|
||||
|
||||
private static (uint[], string[]) LoadStrings(BinaryReader r)
|
||||
{
|
||||
var stringCount = r.ReadUInt16();
|
||||
r.ReadUInt16();
|
||||
var stringSize = (int)r.ReadUInt32();
|
||||
var stringData = r.ReadBytes(stringSize);
|
||||
var start = 0;
|
||||
var strings = new string[stringCount];
|
||||
var offsets = new uint[stringCount];
|
||||
for (var i = 0; i < stringCount; ++i)
|
||||
{
|
||||
var span = stringData.AsSpan(start);
|
||||
var idx = span.IndexOf((byte)'\0');
|
||||
strings[i] = Encoding.UTF8.GetString(span[..idx]);
|
||||
offsets[i] = (uint)start;
|
||||
start = start + idx + 1;
|
||||
}
|
||||
|
||||
return (offsets, strings);
|
||||
}
|
||||
|
||||
public unsafe uint StackSize
|
||||
=> (uint)(VertexDeclarations.Length * NumVertices * sizeof(MdlStructs.VertexElement));
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public partial class MtrlFile
|
||||
{
|
||||
public unsafe struct ColorDyeSet
|
||||
{
|
||||
public struct Row
|
||||
{
|
||||
private ushort _data;
|
||||
|
||||
public ushort Template
|
||||
{
|
||||
get => (ushort)(_data >> 5);
|
||||
set => _data = (ushort)((_data & 0x1F) | (value << 5));
|
||||
}
|
||||
|
||||
public bool Diffuse
|
||||
{
|
||||
get => (_data & 0x01) != 0;
|
||||
set => _data = (ushort)(value ? _data | 0x01 : _data & 0xFFFE);
|
||||
}
|
||||
|
||||
public bool Specular
|
||||
{
|
||||
get => (_data & 0x02) != 0;
|
||||
set => _data = (ushort)(value ? _data | 0x02 : _data & 0xFFFD);
|
||||
}
|
||||
|
||||
public bool Emissive
|
||||
{
|
||||
get => (_data & 0x04) != 0;
|
||||
set => _data = (ushort)(value ? _data | 0x04 : _data & 0xFFFB);
|
||||
}
|
||||
|
||||
public bool Gloss
|
||||
{
|
||||
get => (_data & 0x08) != 0;
|
||||
set => _data = (ushort)(value ? _data | 0x08 : _data & 0xFFF7);
|
||||
}
|
||||
|
||||
public bool SpecularStrength
|
||||
{
|
||||
get => (_data & 0x10) != 0;
|
||||
set => _data = (ushort)(value ? _data | 0x10 : _data & 0xFFEF);
|
||||
}
|
||||
}
|
||||
|
||||
public struct RowArray : IEnumerable<Row>
|
||||
{
|
||||
public const int NumRows = 16;
|
||||
private fixed ushort _rowData[NumRows];
|
||||
|
||||
public ref Row this[int i]
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (ushort* ptr = _rowData)
|
||||
{
|
||||
return ref ((Row*)ptr)[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<Row> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < NumRows; ++i)
|
||||
yield return this[i];
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public ReadOnlySpan<byte> AsBytes()
|
||||
{
|
||||
fixed (ushort* ptr = _rowData)
|
||||
{
|
||||
return new ReadOnlySpan<byte>(ptr, NumRows * sizeof(ushort));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RowArray Rows;
|
||||
public string Name;
|
||||
public ushort Index;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public partial class MtrlFile
|
||||
{
|
||||
public unsafe struct ColorSet
|
||||
{
|
||||
public struct Row
|
||||
{
|
||||
public const int Size = 32;
|
||||
|
||||
private fixed ushort _data[16];
|
||||
|
||||
public Vector3 Diffuse
|
||||
{
|
||||
get => new(ToFloat(0), ToFloat(1), ToFloat(2));
|
||||
set
|
||||
{
|
||||
_data[0] = FromFloat(value.X);
|
||||
_data[1] = FromFloat(value.Y);
|
||||
_data[2] = FromFloat(value.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Specular
|
||||
{
|
||||
get => new(ToFloat(4), ToFloat(5), ToFloat(6));
|
||||
set
|
||||
{
|
||||
_data[4] = FromFloat(value.X);
|
||||
_data[5] = FromFloat(value.Y);
|
||||
_data[6] = FromFloat(value.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Emissive
|
||||
{
|
||||
get => new(ToFloat(8), ToFloat(9), ToFloat(10));
|
||||
set
|
||||
{
|
||||
_data[8] = FromFloat(value.X);
|
||||
_data[9] = FromFloat(value.Y);
|
||||
_data[10] = FromFloat(value.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 MaterialRepeat
|
||||
{
|
||||
get => new(ToFloat(12), ToFloat(15));
|
||||
set
|
||||
{
|
||||
_data[12] = FromFloat(value.X);
|
||||
_data[15] = FromFloat(value.Y);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 MaterialSkew
|
||||
{
|
||||
get => new(ToFloat(13), ToFloat(14));
|
||||
set
|
||||
{
|
||||
_data[13] = FromFloat(value.X);
|
||||
_data[14] = FromFloat(value.Y);
|
||||
}
|
||||
}
|
||||
|
||||
public float SpecularStrength
|
||||
{
|
||||
get => ToFloat(3);
|
||||
set => _data[3] = FromFloat(value);
|
||||
}
|
||||
|
||||
public float GlossStrength
|
||||
{
|
||||
get => ToFloat(7);
|
||||
set => _data[7] = FromFloat(value);
|
||||
}
|
||||
|
||||
public ushort TileSet
|
||||
{
|
||||
get => (ushort)(ToFloat(11) * 64f);
|
||||
set => _data[11] = FromFloat(value / 64f);
|
||||
}
|
||||
|
||||
private float ToFloat(int idx)
|
||||
=> (float)BitConverter.UInt16BitsToHalf(_data[idx]);
|
||||
|
||||
private static ushort FromFloat(float x)
|
||||
=> BitConverter.HalfToUInt16Bits((Half)x);
|
||||
}
|
||||
|
||||
public struct RowArray : IEnumerable<Row>
|
||||
{
|
||||
public const int NumRows = 16;
|
||||
private fixed byte _rowData[NumRows * Row.Size];
|
||||
|
||||
public ref Row this[int i]
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* ptr = _rowData)
|
||||
{
|
||||
return ref ((Row*)ptr)[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<Row> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < NumRows; ++i)
|
||||
yield return this[i];
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public ReadOnlySpan<byte> AsBytes()
|
||||
{
|
||||
fixed (byte* ptr = _rowData)
|
||||
{
|
||||
return new ReadOnlySpan<byte>(ptr, NumRows * Row.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RowArray Rows;
|
||||
public string Name;
|
||||
public ushort Index;
|
||||
public bool HasRows;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public partial class MtrlFile
|
||||
{
|
||||
public byte[] Write()
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
using( var w = new BinaryWriter( stream ) )
|
||||
{
|
||||
const int materialHeaderSize = 4 + 2 + 2 + 2 + 2 + 1 + 1 + 1 + 1;
|
||||
|
||||
w.BaseStream.Seek( materialHeaderSize, SeekOrigin.Begin );
|
||||
ushort cumulativeStringOffset = 0;
|
||||
foreach( var texture in Textures )
|
||||
{
|
||||
w.Write( cumulativeStringOffset );
|
||||
w.Write( texture.Flags );
|
||||
cumulativeStringOffset += ( ushort )( texture.Path.Length + 1 );
|
||||
}
|
||||
|
||||
foreach( var set in UvSets )
|
||||
{
|
||||
w.Write( cumulativeStringOffset );
|
||||
w.Write( set.Index );
|
||||
cumulativeStringOffset += ( ushort )( set.Name.Length + 1 );
|
||||
}
|
||||
|
||||
foreach( var set in ColorSets )
|
||||
{
|
||||
w.Write( cumulativeStringOffset );
|
||||
w.Write( set.Index );
|
||||
cumulativeStringOffset += ( ushort )( set.Name.Length + 1 );
|
||||
}
|
||||
|
||||
foreach( var text in Textures.Select( t => t.Path )
|
||||
.Concat( UvSets.Select( c => c.Name ) )
|
||||
.Concat( ColorSets.Select( c => c.Name ) )
|
||||
.Append( ShaderPackage.Name ) )
|
||||
{
|
||||
w.Write( Encoding.UTF8.GetBytes( text ) );
|
||||
w.Write( ( byte )'\0' );
|
||||
}
|
||||
|
||||
w.Write( AdditionalData );
|
||||
var dataSetSize = 0;
|
||||
foreach( var row in ColorSets.Where( c => c.HasRows ).Select( c => c.Rows ) )
|
||||
{
|
||||
var span = row.AsBytes();
|
||||
w.Write( span );
|
||||
dataSetSize += span.Length;
|
||||
}
|
||||
|
||||
foreach( var row in ColorDyeSets.Select( c => c.Rows ) )
|
||||
{
|
||||
var span = row.AsBytes();
|
||||
w.Write( span );
|
||||
dataSetSize += span.Length;
|
||||
}
|
||||
|
||||
w.Write( ( ushort )( ShaderPackage.ShaderValues.Length * 4 ) );
|
||||
w.Write( ( ushort )ShaderPackage.ShaderKeys.Length );
|
||||
w.Write( ( ushort )ShaderPackage.Constants.Length );
|
||||
w.Write( ( ushort )ShaderPackage.Samplers.Length );
|
||||
w.Write( ShaderPackage.Flags );
|
||||
|
||||
foreach( var key in ShaderPackage.ShaderKeys )
|
||||
{
|
||||
w.Write( key.Category );
|
||||
w.Write( key.Value );
|
||||
}
|
||||
|
||||
foreach( var constant in ShaderPackage.Constants )
|
||||
{
|
||||
w.Write( constant.Id );
|
||||
w.Write( constant.ByteOffset );
|
||||
w.Write( constant.ByteSize );
|
||||
}
|
||||
|
||||
foreach( var sampler in ShaderPackage.Samplers )
|
||||
{
|
||||
w.Write( sampler.SamplerId );
|
||||
w.Write( sampler.Flags );
|
||||
w.Write( sampler.TextureIndex );
|
||||
w.Write( ( ushort )0 );
|
||||
w.Write( ( byte )0 );
|
||||
}
|
||||
|
||||
foreach( var value in ShaderPackage.ShaderValues )
|
||||
{
|
||||
w.Write( value );
|
||||
}
|
||||
|
||||
WriteHeader( w, ( ushort )w.BaseStream.Position, dataSetSize, cumulativeStringOffset );
|
||||
}
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private void WriteHeader( BinaryWriter w, ushort fileSize, int dataSetSize, ushort shaderPackageNameOffset )
|
||||
{
|
||||
w.BaseStream.Seek( 0, SeekOrigin.Begin );
|
||||
w.Write( Version );
|
||||
w.Write( fileSize );
|
||||
w.Write( ( ushort )dataSetSize );
|
||||
w.Write( ( ushort )( shaderPackageNameOffset + ShaderPackage.Name.Length + 1 ) );
|
||||
w.Write( shaderPackageNameOffset );
|
||||
w.Write( ( byte )Textures.Length );
|
||||
w.Write( ( byte )UvSets.Length );
|
||||
w.Write( ( byte )ColorSets.Length );
|
||||
w.Write( ( byte )AdditionalData.Length );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Lumina.Data.Parsing;
|
||||
using Lumina.Extensions;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public partial class MtrlFile : IWritable
|
||||
{
|
||||
public readonly uint Version;
|
||||
|
||||
public bool Valid
|
||||
=> CheckTextures();
|
||||
|
||||
public Texture[] Textures;
|
||||
public UvSet[] UvSets;
|
||||
public ColorSet[] ColorSets;
|
||||
public ColorDyeSet[] ColorDyeSets;
|
||||
public ShaderPackageData ShaderPackage;
|
||||
public byte[] AdditionalData;
|
||||
|
||||
public bool ApplyDyeTemplate(StmFile stm, int colorSetIdx, int rowIdx, StainId stainId)
|
||||
{
|
||||
if (colorSetIdx < 0 || colorSetIdx >= ColorDyeSets.Length || rowIdx is < 0 or >= ColorSet.RowArray.NumRows)
|
||||
return false;
|
||||
|
||||
var dyeSet = ColorDyeSets[colorSetIdx].Rows[rowIdx];
|
||||
if (!stm.TryGetValue(dyeSet.Template, stainId, out var dyes))
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
if (dyeSet.Diffuse && ColorSets[colorSetIdx].Rows[rowIdx].Diffuse != dyes.Diffuse)
|
||||
{
|
||||
ColorSets[colorSetIdx].Rows[rowIdx].Diffuse = dyes.Diffuse;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (dyeSet.Specular && ColorSets[colorSetIdx].Rows[rowIdx].Specular != dyes.Specular)
|
||||
{
|
||||
ColorSets[colorSetIdx].Rows[rowIdx].Specular = dyes.Specular;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (dyeSet.SpecularStrength && ColorSets[colorSetIdx].Rows[rowIdx].SpecularStrength != dyes.SpecularPower)
|
||||
{
|
||||
ColorSets[colorSetIdx].Rows[rowIdx].SpecularStrength = dyes.SpecularPower;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (dyeSet.Emissive && ColorSets[colorSetIdx].Rows[rowIdx].Emissive != dyes.Emissive)
|
||||
{
|
||||
ColorSets[colorSetIdx].Rows[rowIdx].Emissive = dyes.Emissive;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (dyeSet.Gloss && ColorSets[colorSetIdx].Rows[rowIdx].GlossStrength != dyes.Gloss)
|
||||
{
|
||||
ColorSets[colorSetIdx].Rows[rowIdx].GlossStrength = dyes.Gloss;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Span<float> GetConstantValues(Constant constant)
|
||||
{
|
||||
if ((constant.ByteOffset & 0x3) != 0
|
||||
|| (constant.ByteSize & 0x3) != 0
|
||||
|| (constant.ByteOffset + constant.ByteSize) >> 2 > ShaderPackage.ShaderValues.Length)
|
||||
return null;
|
||||
|
||||
return ShaderPackage.ShaderValues.AsSpan().Slice(constant.ByteOffset >> 2, constant.ByteSize >> 2);
|
||||
|
||||
}
|
||||
|
||||
public List<(Sampler?, ShpkFile.Resource?)> GetSamplersByTexture(ShpkFile? shpk)
|
||||
{
|
||||
var samplers = new List<(Sampler?, ShpkFile.Resource?)>();
|
||||
for (var i = 0; i < Textures.Length; ++i)
|
||||
{
|
||||
samplers.Add((null, null));
|
||||
}
|
||||
foreach (var sampler in ShaderPackage.Samplers)
|
||||
{
|
||||
samplers[sampler.TextureIndex] = (sampler, shpk?.GetSamplerById(sampler.SamplerId));
|
||||
}
|
||||
|
||||
return samplers;
|
||||
}
|
||||
|
||||
public MtrlFile(byte[] data)
|
||||
{
|
||||
using var stream = new MemoryStream(data);
|
||||
using var r = new BinaryReader(stream);
|
||||
|
||||
Version = r.ReadUInt32();
|
||||
r.ReadUInt16(); // file size
|
||||
var dataSetSize = r.ReadUInt16();
|
||||
var stringTableSize = r.ReadUInt16();
|
||||
var shaderPackageNameOffset = r.ReadUInt16();
|
||||
var textureCount = r.ReadByte();
|
||||
var uvSetCount = r.ReadByte();
|
||||
var colorSetCount = r.ReadByte();
|
||||
var additionalDataSize = r.ReadByte();
|
||||
|
||||
Textures = ReadTextureOffsets(r, textureCount, out var textureOffsets);
|
||||
UvSets = ReadUvSetOffsets(r, uvSetCount, out var uvOffsets);
|
||||
ColorSets = ReadColorSetOffsets(r, colorSetCount, out var colorOffsets);
|
||||
|
||||
var strings = r.ReadBytes(stringTableSize);
|
||||
for (var i = 0; i < textureCount; ++i)
|
||||
Textures[i].Path = UseOffset(strings, textureOffsets[i]);
|
||||
|
||||
for (var i = 0; i < uvSetCount; ++i)
|
||||
UvSets[i].Name = UseOffset(strings, uvOffsets[i]);
|
||||
|
||||
for (var i = 0; i < colorSetCount; ++i)
|
||||
ColorSets[i].Name = UseOffset(strings, colorOffsets[i]);
|
||||
|
||||
ColorDyeSets = ColorSets.Length * ColorSet.RowArray.NumRows * ColorSet.Row.Size < dataSetSize
|
||||
? ColorSets.Select(c => new ColorDyeSet
|
||||
{
|
||||
Index = c.Index,
|
||||
Name = c.Name,
|
||||
}).ToArray()
|
||||
: Array.Empty<ColorDyeSet>();
|
||||
|
||||
ShaderPackage.Name = UseOffset(strings, shaderPackageNameOffset);
|
||||
|
||||
AdditionalData = r.ReadBytes(additionalDataSize);
|
||||
for (var i = 0; i < ColorSets.Length; ++i)
|
||||
{
|
||||
if (stream.Position + ColorSet.RowArray.NumRows * ColorSet.Row.Size <= stream.Length)
|
||||
{
|
||||
ColorSets[i].Rows = r.ReadStructure<ColorSet.RowArray>();
|
||||
ColorSets[i].HasRows = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ColorSets[i].HasRows = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < ColorDyeSets.Length; ++i)
|
||||
ColorDyeSets[i].Rows = r.ReadStructure<ColorDyeSet.RowArray>();
|
||||
|
||||
var shaderValueListSize = r.ReadUInt16();
|
||||
var shaderKeyCount = r.ReadUInt16();
|
||||
var constantCount = r.ReadUInt16();
|
||||
var samplerCount = r.ReadUInt16();
|
||||
ShaderPackage.Flags = r.ReadUInt32();
|
||||
|
||||
ShaderPackage.ShaderKeys = r.ReadStructuresAsArray<ShaderKey>(shaderKeyCount);
|
||||
ShaderPackage.Constants = r.ReadStructuresAsArray<Constant>(constantCount);
|
||||
ShaderPackage.Samplers = r.ReadStructuresAsArray<Sampler>(samplerCount);
|
||||
ShaderPackage.ShaderValues = r.ReadStructuresAsArray<float>(shaderValueListSize / 4);
|
||||
}
|
||||
|
||||
private static Texture[] ReadTextureOffsets(BinaryReader r, int count, out ushort[] offsets)
|
||||
{
|
||||
var ret = new Texture[count];
|
||||
offsets = new ushort[count];
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
offsets[i] = r.ReadUInt16();
|
||||
ret[i].Flags = r.ReadUInt16();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static UvSet[] ReadUvSetOffsets(BinaryReader r, int count, out ushort[] offsets)
|
||||
{
|
||||
var ret = new UvSet[count];
|
||||
offsets = new ushort[count];
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
offsets[i] = r.ReadUInt16();
|
||||
ret[i].Index = r.ReadUInt16();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static ColorSet[] ReadColorSetOffsets(BinaryReader r, int count, out ushort[] offsets)
|
||||
{
|
||||
var ret = new ColorSet[count];
|
||||
offsets = new ushort[count];
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
offsets[i] = r.ReadUInt16();
|
||||
ret[i].Index = r.ReadUInt16();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static string UseOffset(ReadOnlySpan<byte> strings, ushort offset)
|
||||
{
|
||||
strings = strings[offset..];
|
||||
var end = strings.IndexOf((byte)'\0');
|
||||
return Encoding.UTF8.GetString(end == -1 ? strings : strings[..end]);
|
||||
}
|
||||
|
||||
private bool CheckTextures()
|
||||
=> Textures.All(texture => texture.Path.Contains('/'));
|
||||
|
||||
public struct UvSet
|
||||
{
|
||||
public string Name;
|
||||
public ushort Index;
|
||||
}
|
||||
|
||||
public struct Texture
|
||||
{
|
||||
public string Path;
|
||||
public ushort Flags;
|
||||
|
||||
public bool DX11
|
||||
=> (Flags & 0x8000) != 0;
|
||||
}
|
||||
|
||||
public struct Constant
|
||||
{
|
||||
public uint Id;
|
||||
public ushort ByteOffset;
|
||||
public ushort ByteSize;
|
||||
}
|
||||
|
||||
public struct ShaderPackageData
|
||||
{
|
||||
public string Name;
|
||||
public ShaderKey[] ShaderKeys;
|
||||
public Constant[] Constants;
|
||||
public Sampler[] Samplers;
|
||||
public float[] ShaderValues;
|
||||
public uint Flags;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,220 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Lumina.Misc;
|
||||
using Penumbra.GameData.Data;
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public partial class ShpkFile
|
||||
{
|
||||
public struct Shader
|
||||
{
|
||||
public DisassembledShader.ShaderStage Stage;
|
||||
public DxVersion DirectXVersion;
|
||||
public Resource[] Constants;
|
||||
public Resource[] Samplers;
|
||||
public Resource[] Uavs;
|
||||
public byte[] AdditionalHeader;
|
||||
private byte[] _byteData;
|
||||
private DisassembledShader? _disassembly;
|
||||
|
||||
public byte[] Blob
|
||||
{
|
||||
get => _byteData;
|
||||
set
|
||||
{
|
||||
if (_byteData == value)
|
||||
return;
|
||||
|
||||
if (Stage != DisassembledShader.ShaderStage.Unspecified)
|
||||
{
|
||||
// Reject the blob entirely if we can't disassemble it or if we find inconsistencies.
|
||||
var disasm = DisassembledShader.Disassemble(value);
|
||||
if (disasm.Stage != Stage || (disasm.ShaderModel >> 8) + 6 != (uint)DirectXVersion)
|
||||
throw new ArgumentException(
|
||||
$"The supplied blob is a DirectX {(disasm.ShaderModel >> 8) + 6} {disasm.Stage} shader ; expected a DirectX {(uint)DirectXVersion} {Stage} shader.",
|
||||
nameof(value));
|
||||
|
||||
if (disasm.ShaderModel >= 0x0500)
|
||||
{
|
||||
var samplers = new Dictionary<uint, string>();
|
||||
var textures = new Dictionary<uint, string>();
|
||||
foreach (var binding in disasm.ResourceBindings)
|
||||
{
|
||||
switch (binding.Type)
|
||||
{
|
||||
case DisassembledShader.ResourceType.Texture:
|
||||
textures[binding.Slot] = NormalizeResourceName(binding.Name);
|
||||
break;
|
||||
case DisassembledShader.ResourceType.Sampler:
|
||||
samplers[binding.Slot] = NormalizeResourceName(binding.Name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (samplers.Count != textures.Count
|
||||
|| !samplers.All(pair => textures.TryGetValue(pair.Key, out var texName) && pair.Value == texName))
|
||||
throw new ArgumentException($"The supplied blob has inconsistent sampler and texture allocation.");
|
||||
}
|
||||
|
||||
_byteData = value;
|
||||
_disassembly = disasm;
|
||||
}
|
||||
else
|
||||
{
|
||||
_byteData = value;
|
||||
_disassembly = null;
|
||||
}
|
||||
|
||||
UpdateUsed();
|
||||
}
|
||||
}
|
||||
|
||||
public DisassembledShader? Disassembly
|
||||
=> _disassembly;
|
||||
|
||||
public Resource? GetConstantById(uint id)
|
||||
=> Constants.FirstOrNull(res => res.Id == id);
|
||||
|
||||
public Resource? GetConstantByName(string name)
|
||||
=> Constants.FirstOrNull(res => res.Name == name);
|
||||
|
||||
public Resource? GetSamplerById(uint id)
|
||||
=> Samplers.FirstOrNull(s => s.Id == id);
|
||||
|
||||
public Resource? GetSamplerByName(string name)
|
||||
=> Samplers.FirstOrNull(s => s.Name == name);
|
||||
|
||||
public Resource? GetUavById(uint id)
|
||||
=> Uavs.FirstOrNull(u => u.Id == id);
|
||||
|
||||
public Resource? GetUavByName(string name)
|
||||
=> Uavs.FirstOrNull(u => u.Name == name);
|
||||
|
||||
public void UpdateResources(ShpkFile file)
|
||||
{
|
||||
if (_disassembly == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
var constants = new List<Resource>();
|
||||
var samplers = new List<Resource>();
|
||||
var uavs = new List<Resource>();
|
||||
foreach (var binding in _disassembly.ResourceBindings)
|
||||
{
|
||||
switch (binding.Type)
|
||||
{
|
||||
case DisassembledShader.ResourceType.ConstantBuffer:
|
||||
var name = NormalizeResourceName(binding.Name);
|
||||
// We want to preserve IDs as much as possible, and to deterministically generate new ones in a way that's most compliant with the native ones, to maximize compatibility.
|
||||
var id = GetConstantByName(name)?.Id ?? file.GetConstantByName(name)?.Id ?? Crc32.Get(name, 0xFFFFFFFFu);
|
||||
constants.Add(new Resource
|
||||
{
|
||||
Id = id,
|
||||
Name = name,
|
||||
Slot = (ushort)binding.Slot,
|
||||
Size = (ushort)binding.RegisterCount,
|
||||
Used = binding.Used,
|
||||
UsedDynamically = binding.UsedDynamically,
|
||||
});
|
||||
break;
|
||||
case DisassembledShader.ResourceType.Texture:
|
||||
name = NormalizeResourceName(binding.Name);
|
||||
id = GetSamplerByName(name)?.Id ?? file.GetSamplerByName(name)?.Id ?? Crc32.Get(name, 0xFFFFFFFFu);
|
||||
samplers.Add(new Resource
|
||||
{
|
||||
Id = id,
|
||||
Name = name,
|
||||
Slot = (ushort)binding.Slot,
|
||||
Size = (ushort)binding.Slot,
|
||||
Used = binding.Used,
|
||||
UsedDynamically = binding.UsedDynamically,
|
||||
});
|
||||
break;
|
||||
case DisassembledShader.ResourceType.Uav:
|
||||
name = NormalizeResourceName(binding.Name);
|
||||
id = GetUavByName(name)?.Id ?? file.GetUavByName(name)?.Id ?? Crc32.Get(name, 0xFFFFFFFFu);
|
||||
uavs.Add(new Resource
|
||||
{
|
||||
Id = id,
|
||||
Name = name,
|
||||
Slot = (ushort)binding.Slot,
|
||||
Size = (ushort)binding.Slot,
|
||||
Used = binding.Used,
|
||||
UsedDynamically = binding.UsedDynamically,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Constants = constants.ToArray();
|
||||
Samplers = samplers.ToArray();
|
||||
Uavs = uavs.ToArray();
|
||||
}
|
||||
|
||||
private void UpdateUsed()
|
||||
{
|
||||
if (_disassembly != null)
|
||||
{
|
||||
var cbUsage = new Dictionary<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
|
||||
var tUsage = new Dictionary<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
|
||||
var uUsage = new Dictionary<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
|
||||
foreach (var binding in _disassembly.ResourceBindings)
|
||||
{
|
||||
switch (binding.Type)
|
||||
{
|
||||
case DisassembledShader.ResourceType.ConstantBuffer:
|
||||
cbUsage[NormalizeResourceName(binding.Name)] = (binding.Used, binding.UsedDynamically);
|
||||
break;
|
||||
case DisassembledShader.ResourceType.Texture:
|
||||
tUsage[NormalizeResourceName(binding.Name)] = (binding.Used, binding.UsedDynamically);
|
||||
break;
|
||||
case DisassembledShader.ResourceType.Uav:
|
||||
uUsage[NormalizeResourceName(binding.Name)] = (binding.Used, binding.UsedDynamically);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void CopyUsed(Resource[] resources,
|
||||
Dictionary<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)> used)
|
||||
{
|
||||
for (var i = 0; i < resources.Length; ++i)
|
||||
{
|
||||
if (used.TryGetValue(resources[i].Name, out var usage))
|
||||
{
|
||||
resources[i].Used = usage.Item1;
|
||||
resources[i].UsedDynamically = usage.Item2;
|
||||
}
|
||||
else
|
||||
{
|
||||
resources[i].Used = null;
|
||||
resources[i].UsedDynamically = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CopyUsed(Constants, cbUsage);
|
||||
CopyUsed(Samplers, tUsage);
|
||||
CopyUsed(Uavs, uUsage);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearUsed(Constants);
|
||||
ClearUsed(Samplers);
|
||||
ClearUsed(Uavs);
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeResourceName(string resourceName)
|
||||
{
|
||||
var dot = resourceName.IndexOf('.');
|
||||
if (dot >= 0)
|
||||
return resourceName[..dot];
|
||||
if (resourceName.Length > 1 && resourceName[^2] is '_' && resourceName[^1] is 'S' or 'T')
|
||||
return resourceName[..^2];
|
||||
|
||||
return resourceName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public partial class ShpkFile
|
||||
{
|
||||
public class StringPool
|
||||
{
|
||||
public MemoryStream Data;
|
||||
public List<int> StartingOffsets;
|
||||
|
||||
public StringPool(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
Data = new MemoryStream();
|
||||
Data.Write(bytes);
|
||||
StartingOffsets = new List<int>
|
||||
{
|
||||
0,
|
||||
};
|
||||
for (var i = 0; i < bytes.Length; ++i)
|
||||
{
|
||||
if (bytes[i] == 0)
|
||||
StartingOffsets.Add(i + 1);
|
||||
}
|
||||
|
||||
if (StartingOffsets[^1] == bytes.Length)
|
||||
StartingOffsets.RemoveAt(StartingOffsets.Count - 1);
|
||||
else
|
||||
Data.WriteByte(0);
|
||||
}
|
||||
|
||||
public string GetString(int offset, int size)
|
||||
=> Encoding.UTF8.GetString(Data.GetBuffer().AsSpan().Slice(offset, size));
|
||||
|
||||
public string GetNullTerminatedString(int offset)
|
||||
{
|
||||
var str = Data.GetBuffer().AsSpan()[offset..];
|
||||
var size = str.IndexOf((byte)0);
|
||||
if (size >= 0)
|
||||
str = str[..size];
|
||||
return Encoding.UTF8.GetString(str);
|
||||
}
|
||||
|
||||
public (int, int) FindOrAddString(string str)
|
||||
{
|
||||
var dataSpan = Data.GetBuffer().AsSpan();
|
||||
var bytes = Encoding.UTF8.GetBytes(str);
|
||||
foreach (var offset in StartingOffsets)
|
||||
{
|
||||
if (offset + bytes.Length > Data.Length)
|
||||
break;
|
||||
|
||||
var strSpan = dataSpan[offset..];
|
||||
var match = true;
|
||||
for (var i = 0; i < bytes.Length; ++i)
|
||||
{
|
||||
if (strSpan[i] != bytes[i])
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match && strSpan[bytes.Length] == 0)
|
||||
return (offset, bytes.Length);
|
||||
}
|
||||
|
||||
Data.Seek(0L, SeekOrigin.End);
|
||||
var newOffset = (int)Data.Position;
|
||||
StartingOffsets.Add(newOffset);
|
||||
Data.Write(bytes);
|
||||
Data.WriteByte(0);
|
||||
return (newOffset, bytes.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public partial class ShpkFile
|
||||
{
|
||||
public byte[] Write()
|
||||
{
|
||||
if (SubViewKeys.Length != 2)
|
||||
throw new InvalidDataException();
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
using var blobs = new MemoryStream();
|
||||
var strings = new StringPool(ReadOnlySpan<byte>.Empty);
|
||||
using (var w = new BinaryWriter(stream))
|
||||
{
|
||||
w.Write(ShPkMagic);
|
||||
w.Write(Version);
|
||||
w.Write(DirectXVersion switch
|
||||
{
|
||||
DxVersion.DirectX9 => Dx9Magic,
|
||||
DxVersion.DirectX11 => Dx11Magic,
|
||||
_ => throw new NotImplementedException(),
|
||||
});
|
||||
var offsetsPosition = stream.Position;
|
||||
w.Write(0u); // Placeholder for file size
|
||||
w.Write(0u); // Placeholder for blobs offset
|
||||
w.Write(0u); // Placeholder for strings offset
|
||||
w.Write((uint)VertexShaders.Length);
|
||||
w.Write((uint)PixelShaders.Length);
|
||||
w.Write(MaterialParamsSize);
|
||||
w.Write((uint)MaterialParams.Length);
|
||||
w.Write((uint)Constants.Length);
|
||||
w.Write((uint)Samplers.Length);
|
||||
w.Write((uint)Uavs.Length);
|
||||
w.Write((uint)SystemKeys.Length);
|
||||
w.Write((uint)SceneKeys.Length);
|
||||
w.Write((uint)MaterialKeys.Length);
|
||||
w.Write((uint)Nodes.Length);
|
||||
w.Write((uint)Items.Length);
|
||||
|
||||
WriteShaderArray(w, VertexShaders, blobs, strings);
|
||||
WriteShaderArray(w, PixelShaders, blobs, strings);
|
||||
|
||||
foreach (var materialParam in MaterialParams)
|
||||
{
|
||||
w.Write(materialParam.Id);
|
||||
w.Write(materialParam.ByteOffset);
|
||||
w.Write(materialParam.ByteSize);
|
||||
}
|
||||
|
||||
WriteResourceArray(w, Constants, strings);
|
||||
WriteResourceArray(w, Samplers, strings);
|
||||
WriteResourceArray(w, Uavs, strings);
|
||||
|
||||
foreach (var key in SystemKeys)
|
||||
{
|
||||
w.Write(key.Id);
|
||||
w.Write(key.DefaultValue);
|
||||
}
|
||||
|
||||
foreach (var key in SceneKeys)
|
||||
{
|
||||
w.Write(key.Id);
|
||||
w.Write(key.DefaultValue);
|
||||
}
|
||||
|
||||
foreach (var key in MaterialKeys)
|
||||
{
|
||||
w.Write(key.Id);
|
||||
w.Write(key.DefaultValue);
|
||||
}
|
||||
|
||||
foreach (var key in SubViewKeys)
|
||||
w.Write(key.DefaultValue);
|
||||
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
if (node.PassIndices.Length != 16
|
||||
|| node.SystemKeys.Length != SystemKeys.Length
|
||||
|| node.SceneKeys.Length != SceneKeys.Length
|
||||
|| node.MaterialKeys.Length != MaterialKeys.Length
|
||||
|| node.SubViewKeys.Length != SubViewKeys.Length)
|
||||
throw new InvalidDataException();
|
||||
|
||||
w.Write(node.Id);
|
||||
w.Write(node.Passes.Length);
|
||||
w.Write(node.PassIndices);
|
||||
foreach (var key in node.SystemKeys)
|
||||
w.Write(key);
|
||||
foreach (var key in node.SceneKeys)
|
||||
w.Write(key);
|
||||
foreach (var key in node.MaterialKeys)
|
||||
w.Write(key);
|
||||
foreach (var key in node.SubViewKeys)
|
||||
w.Write(key);
|
||||
foreach (var pass in node.Passes)
|
||||
{
|
||||
w.Write(pass.Id);
|
||||
w.Write(pass.VertexShader);
|
||||
w.Write(pass.PixelShader);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in Items)
|
||||
{
|
||||
w.Write(item.Id);
|
||||
w.Write(item.Node);
|
||||
}
|
||||
|
||||
w.Write(AdditionalData);
|
||||
|
||||
var blobsOffset = (int)stream.Position;
|
||||
blobs.WriteTo(stream);
|
||||
|
||||
var stringsOffset = (int)stream.Position;
|
||||
strings.Data.WriteTo(stream);
|
||||
|
||||
var fileSize = (int)stream.Position;
|
||||
|
||||
stream.Seek(offsetsPosition, SeekOrigin.Begin);
|
||||
w.Write(fileSize);
|
||||
w.Write(blobsOffset);
|
||||
w.Write(stringsOffset);
|
||||
}
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private static void WriteResourceArray(BinaryWriter w, Resource[] array, StringPool strings)
|
||||
{
|
||||
foreach (var buf in array)
|
||||
{
|
||||
var (strOffset, strSize) = strings.FindOrAddString(buf.Name);
|
||||
w.Write(buf.Id);
|
||||
w.Write(strOffset);
|
||||
w.Write(strSize);
|
||||
w.Write(buf.Slot);
|
||||
w.Write(buf.Size);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteShaderArray(BinaryWriter w, Shader[] array, MemoryStream blobs, StringPool strings)
|
||||
{
|
||||
foreach (var shader in array)
|
||||
{
|
||||
var blobOffset = (int)blobs.Position;
|
||||
blobs.Write(shader.AdditionalHeader);
|
||||
blobs.Write(shader.Blob);
|
||||
var blobSize = (int)blobs.Position - blobOffset;
|
||||
|
||||
w.Write(blobOffset);
|
||||
w.Write(blobSize);
|
||||
w.Write((ushort)shader.Constants.Length);
|
||||
w.Write((ushort)shader.Samplers.Length);
|
||||
w.Write((ushort)shader.Uavs.Length);
|
||||
w.Write((ushort)0);
|
||||
|
||||
WriteResourceArray(w, shader.Constants, strings);
|
||||
WriteResourceArray(w, shader.Samplers, strings);
|
||||
WriteResourceArray(w, shader.Uavs, strings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,486 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Lumina.Extensions;
|
||||
using Penumbra.GameData.Data;
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public partial class ShpkFile : IWritable
|
||||
{
|
||||
private const uint ShPkMagic = 0x6B506853u; // bytes of ShPk
|
||||
private const uint Dx9Magic = 0x00395844u; // bytes of DX9\0
|
||||
private const uint Dx11Magic = 0x31315844u; // bytes of DX11
|
||||
|
||||
public const uint MaterialParamsConstantId = 0x64D12851u;
|
||||
|
||||
public uint Version;
|
||||
public DxVersion DirectXVersion;
|
||||
public Shader[] VertexShaders;
|
||||
public Shader[] PixelShaders;
|
||||
public uint MaterialParamsSize;
|
||||
public MaterialParam[] MaterialParams;
|
||||
public Resource[] Constants;
|
||||
public Resource[] Samplers;
|
||||
public Resource[] Uavs;
|
||||
public Key[] SystemKeys;
|
||||
public Key[] SceneKeys;
|
||||
public Key[] MaterialKeys;
|
||||
public Key[] SubViewKeys;
|
||||
public Node[] Nodes;
|
||||
public Item[] Items;
|
||||
public byte[] AdditionalData;
|
||||
|
||||
public bool Valid { get; private set; }
|
||||
private bool _changed;
|
||||
|
||||
public MaterialParam? GetMaterialParamById(uint id)
|
||||
=> MaterialParams.FirstOrNull(m => m.Id == id);
|
||||
|
||||
public Resource? GetConstantById(uint id)
|
||||
=> Constants.FirstOrNull(c => c.Id == id);
|
||||
|
||||
public Resource? GetConstantByName(string name)
|
||||
=> Constants.FirstOrNull(c => c.Name == name);
|
||||
|
||||
public Resource? GetSamplerById(uint id)
|
||||
=> Samplers.FirstOrNull(s => s.Id == id);
|
||||
|
||||
public Resource? GetSamplerByName(string name)
|
||||
=> Samplers.FirstOrNull(s => s.Name == name);
|
||||
|
||||
public Resource? GetUavById(uint id)
|
||||
=> Uavs.FirstOrNull(u => u.Id == id);
|
||||
|
||||
public Resource? GetUavByName(string name)
|
||||
=> Uavs.FirstOrNull(u => u.Name == name);
|
||||
|
||||
public Key? GetSystemKeyById(uint id)
|
||||
=> SystemKeys.FirstOrNull(k => k.Id == id);
|
||||
|
||||
public Key? GetSceneKeyById(uint id)
|
||||
=> SceneKeys.FirstOrNull(k => k.Id == id);
|
||||
|
||||
public Key? GetMaterialKeyById(uint id)
|
||||
=> MaterialKeys.FirstOrNull(k => k.Id == id);
|
||||
|
||||
public Node? GetNodeById(uint id)
|
||||
=> Nodes.FirstOrNull(n => n.Id == id);
|
||||
|
||||
public Item? GetItemById(uint id)
|
||||
=> Items.FirstOrNull(i => i.Id == id);
|
||||
|
||||
public ShpkFile(byte[] data, bool disassemble = false)
|
||||
{
|
||||
using var stream = new MemoryStream(data);
|
||||
using var r = new BinaryReader(stream);
|
||||
|
||||
if (r.ReadUInt32() != ShPkMagic)
|
||||
throw new InvalidDataException();
|
||||
|
||||
Version = r.ReadUInt32();
|
||||
DirectXVersion = r.ReadUInt32() switch
|
||||
{
|
||||
Dx9Magic => DxVersion.DirectX9,
|
||||
Dx11Magic => DxVersion.DirectX11,
|
||||
_ => throw new InvalidDataException(),
|
||||
};
|
||||
if (r.ReadUInt32() != data.Length)
|
||||
throw new InvalidDataException();
|
||||
|
||||
var blobsOffset = r.ReadUInt32();
|
||||
var stringsOffset = r.ReadUInt32();
|
||||
var vertexShaderCount = r.ReadUInt32();
|
||||
var pixelShaderCount = r.ReadUInt32();
|
||||
MaterialParamsSize = r.ReadUInt32();
|
||||
var materialParamCount = r.ReadUInt32();
|
||||
var constantCount = r.ReadUInt32();
|
||||
var samplerCount = r.ReadUInt32();
|
||||
var uavCount = r.ReadUInt32();
|
||||
var systemKeyCount = r.ReadUInt32();
|
||||
var sceneKeyCount = r.ReadUInt32();
|
||||
var materialKeyCount = r.ReadUInt32();
|
||||
var nodeCount = r.ReadUInt32();
|
||||
var itemCount = r.ReadUInt32();
|
||||
|
||||
var blobs = new ReadOnlySpan<byte>(data, (int)blobsOffset, (int)(stringsOffset - blobsOffset));
|
||||
var strings = new StringPool(new ReadOnlySpan<byte>(data, (int)stringsOffset, (int)(data.Length - stringsOffset)));
|
||||
|
||||
VertexShaders = ReadShaderArray(r, (int)vertexShaderCount, DisassembledShader.ShaderStage.Vertex, DirectXVersion, disassemble, blobs,
|
||||
strings);
|
||||
PixelShaders = ReadShaderArray(r, (int)pixelShaderCount, DisassembledShader.ShaderStage.Pixel, DirectXVersion, disassemble, blobs,
|
||||
strings);
|
||||
|
||||
MaterialParams = r.ReadStructuresAsArray<MaterialParam>((int)materialParamCount);
|
||||
|
||||
Constants = ReadResourceArray(r, (int)constantCount, strings);
|
||||
Samplers = ReadResourceArray(r, (int)samplerCount, strings);
|
||||
Uavs = ReadResourceArray(r, (int)uavCount, strings);
|
||||
|
||||
SystemKeys = ReadKeyArray(r, (int)systemKeyCount);
|
||||
SceneKeys = ReadKeyArray(r, (int)sceneKeyCount);
|
||||
MaterialKeys = ReadKeyArray(r, (int)materialKeyCount);
|
||||
|
||||
var subViewKey1Default = r.ReadUInt32();
|
||||
var subViewKey2Default = r.ReadUInt32();
|
||||
|
||||
SubViewKeys = new Key[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = 1,
|
||||
DefaultValue = subViewKey1Default,
|
||||
Values = Array.Empty<uint>(),
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 2,
|
||||
DefaultValue = subViewKey2Default,
|
||||
Values = Array.Empty<uint>(),
|
||||
},
|
||||
};
|
||||
|
||||
Nodes = ReadNodeArray(r, (int)nodeCount, SystemKeys.Length, SceneKeys.Length, MaterialKeys.Length, SubViewKeys.Length);
|
||||
Items = r.ReadStructuresAsArray<Item>((int)itemCount);
|
||||
|
||||
AdditionalData = r.ReadBytes((int)(blobsOffset - r.BaseStream.Position)); // This should be empty, but just in case.
|
||||
|
||||
if (disassemble)
|
||||
UpdateUsed();
|
||||
|
||||
UpdateKeyValues();
|
||||
|
||||
Valid = true;
|
||||
_changed = false;
|
||||
}
|
||||
|
||||
public void UpdateResources()
|
||||
{
|
||||
var constants = new Dictionary<uint, Resource>();
|
||||
var samplers = new Dictionary<uint, Resource>();
|
||||
var uavs = new Dictionary<uint, Resource>();
|
||||
|
||||
static void CollectResources(Dictionary<uint, Resource> resources, Resource[] shaderResources, Func<uint, Resource?> getExistingById,
|
||||
DisassembledShader.ResourceType type)
|
||||
{
|
||||
foreach (var resource in shaderResources)
|
||||
{
|
||||
if (resources.TryGetValue(resource.Id, out var carry) && type != DisassembledShader.ResourceType.ConstantBuffer)
|
||||
continue;
|
||||
|
||||
var existing = getExistingById(resource.Id);
|
||||
resources[resource.Id] = new Resource
|
||||
{
|
||||
Id = resource.Id,
|
||||
Name = resource.Name,
|
||||
Slot = existing?.Slot ?? (type == DisassembledShader.ResourceType.ConstantBuffer ? (ushort)65535 : (ushort)2),
|
||||
Size = type == DisassembledShader.ResourceType.ConstantBuffer ? Math.Max(carry.Size, resource.Size) : existing?.Size ?? 0,
|
||||
Used = null,
|
||||
UsedDynamically = null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var shader in VertexShaders)
|
||||
{
|
||||
CollectResources(constants, shader.Constants, GetConstantById, DisassembledShader.ResourceType.ConstantBuffer);
|
||||
CollectResources(samplers, shader.Samplers, GetSamplerById, DisassembledShader.ResourceType.Sampler);
|
||||
CollectResources(uavs, shader.Uavs, GetUavById, DisassembledShader.ResourceType.Uav);
|
||||
}
|
||||
|
||||
foreach (var shader in PixelShaders)
|
||||
{
|
||||
CollectResources(constants, shader.Constants, GetConstantById, DisassembledShader.ResourceType.ConstantBuffer);
|
||||
CollectResources(samplers, shader.Samplers, GetSamplerById, DisassembledShader.ResourceType.Sampler);
|
||||
CollectResources(uavs, shader.Uavs, GetUavById, DisassembledShader.ResourceType.Uav);
|
||||
}
|
||||
|
||||
Constants = constants.Values.ToArray();
|
||||
Samplers = samplers.Values.ToArray();
|
||||
Uavs = uavs.Values.ToArray();
|
||||
UpdateUsed();
|
||||
|
||||
// Ceil required size to a multiple of 16 bytes.
|
||||
// Offsets can be skipped, MaterialParamsConstantId's size is the count.
|
||||
MaterialParamsSize = (GetConstantById(MaterialParamsConstantId)?.Size ?? 0u) << 4;
|
||||
foreach (var param in MaterialParams)
|
||||
MaterialParamsSize = Math.Max(MaterialParamsSize, (uint)param.ByteOffset + param.ByteSize);
|
||||
MaterialParamsSize = (MaterialParamsSize + 0xFu) & ~0xFu;
|
||||
}
|
||||
|
||||
private void UpdateUsed()
|
||||
{
|
||||
var cUsage = new Dictionary<uint, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
|
||||
var sUsage = new Dictionary<uint, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
|
||||
var uUsage = new Dictionary<uint, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
|
||||
|
||||
static void CollectUsed(Dictionary<uint, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)> usage,
|
||||
Resource[] resources)
|
||||
{
|
||||
foreach (var resource in resources)
|
||||
{
|
||||
if (resource.Used == null)
|
||||
continue;
|
||||
|
||||
usage.TryGetValue(resource.Id, out var carry);
|
||||
carry.Item1 ??= Array.Empty<DisassembledShader.VectorComponents>();
|
||||
var combined = new DisassembledShader.VectorComponents[Math.Max(carry.Item1.Length, resource.Used.Length)];
|
||||
for (var i = 0; i < combined.Length; ++i)
|
||||
combined[i] = (i < carry.Item1.Length ? carry.Item1[i] : 0) | (i < resource.Used.Length ? resource.Used[i] : 0);
|
||||
usage[resource.Id] = (combined, carry.Item2 | (resource.UsedDynamically ?? 0));
|
||||
}
|
||||
}
|
||||
|
||||
static void CopyUsed(Resource[] resources,
|
||||
Dictionary<uint, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)> used)
|
||||
{
|
||||
for (var i = 0; i < resources.Length; ++i)
|
||||
{
|
||||
if (used.TryGetValue(resources[i].Id, out var usage))
|
||||
{
|
||||
resources[i].Used = usage.Item1;
|
||||
resources[i].UsedDynamically = usage.Item2;
|
||||
}
|
||||
else
|
||||
{
|
||||
resources[i].Used = null;
|
||||
resources[i].UsedDynamically = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var shader in VertexShaders)
|
||||
{
|
||||
CollectUsed(cUsage, shader.Constants);
|
||||
CollectUsed(sUsage, shader.Samplers);
|
||||
CollectUsed(uUsage, shader.Uavs);
|
||||
}
|
||||
|
||||
foreach (var shader in PixelShaders)
|
||||
{
|
||||
CollectUsed(cUsage, shader.Constants);
|
||||
CollectUsed(sUsage, shader.Samplers);
|
||||
CollectUsed(uUsage, shader.Uavs);
|
||||
}
|
||||
|
||||
CopyUsed(Constants, cUsage);
|
||||
CopyUsed(Samplers, sUsage);
|
||||
CopyUsed(Uavs, uUsage);
|
||||
}
|
||||
|
||||
public void UpdateKeyValues()
|
||||
{
|
||||
static HashSet<uint>[] InitializeValueSet(Key[] keys)
|
||||
=> Array.ConvertAll(keys, key => new HashSet<uint>()
|
||||
{
|
||||
key.DefaultValue,
|
||||
});
|
||||
|
||||
static void CollectValues(HashSet<uint>[] valueSets, uint[] values)
|
||||
{
|
||||
for (var i = 0; i < valueSets.Length; ++i)
|
||||
valueSets[i].Add(values[i]);
|
||||
}
|
||||
|
||||
static void CopyValues(Key[] keys, HashSet<uint>[] valueSets)
|
||||
{
|
||||
for (var i = 0; i < keys.Length; ++i)
|
||||
keys[i].Values = valueSets[i].ToArray();
|
||||
}
|
||||
|
||||
var systemKeyValues = InitializeValueSet(SystemKeys);
|
||||
var sceneKeyValues = InitializeValueSet(SceneKeys);
|
||||
var materialKeyValues = InitializeValueSet(MaterialKeys);
|
||||
var subViewKeyValues = InitializeValueSet(SubViewKeys);
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
CollectValues(systemKeyValues, node.SystemKeys);
|
||||
CollectValues(sceneKeyValues, node.SceneKeys);
|
||||
CollectValues(materialKeyValues, node.MaterialKeys);
|
||||
CollectValues(subViewKeyValues, node.SubViewKeys);
|
||||
}
|
||||
|
||||
CopyValues(SystemKeys, systemKeyValues);
|
||||
CopyValues(SceneKeys, sceneKeyValues);
|
||||
CopyValues(MaterialKeys, materialKeyValues);
|
||||
CopyValues(SubViewKeys, subViewKeyValues);
|
||||
}
|
||||
|
||||
public void SetInvalid()
|
||||
=> Valid = false;
|
||||
|
||||
public void SetChanged()
|
||||
=> _changed = true;
|
||||
|
||||
public bool IsChanged()
|
||||
{
|
||||
var changed = _changed;
|
||||
_changed = false;
|
||||
return changed;
|
||||
}
|
||||
|
||||
private static void ClearUsed(Resource[] resources)
|
||||
{
|
||||
for (var i = 0; i < resources.Length; ++i)
|
||||
{
|
||||
resources[i].Used = null;
|
||||
resources[i].UsedDynamically = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Resource[] ReadResourceArray(BinaryReader r, int count, StringPool strings)
|
||||
{
|
||||
var ret = new Resource[count];
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var id = r.ReadUInt32();
|
||||
var strOffset = r.ReadUInt32();
|
||||
var strSize = r.ReadUInt32();
|
||||
ret[i] = new Resource
|
||||
{
|
||||
Id = id,
|
||||
Name = strings.GetString((int)strOffset, (int)strSize),
|
||||
Slot = r.ReadUInt16(),
|
||||
Size = r.ReadUInt16(),
|
||||
};
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Shader[] ReadShaderArray(BinaryReader r, int count, DisassembledShader.ShaderStage stage, DxVersion directX,
|
||||
bool disassemble, ReadOnlySpan<byte> blobs, StringPool strings)
|
||||
{
|
||||
var extraHeaderSize = stage switch
|
||||
{
|
||||
DisassembledShader.ShaderStage.Vertex => directX switch
|
||||
{
|
||||
DxVersion.DirectX9 => 4,
|
||||
DxVersion.DirectX11 => 8,
|
||||
_ => throw new NotImplementedException(),
|
||||
},
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
var ret = new Shader[count];
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var blobOffset = r.ReadUInt32();
|
||||
var blobSize = r.ReadUInt32();
|
||||
var constantCount = r.ReadUInt16();
|
||||
var samplerCount = r.ReadUInt16();
|
||||
var uavCount = r.ReadUInt16();
|
||||
if (r.ReadUInt16() != 0)
|
||||
throw new NotImplementedException();
|
||||
|
||||
var rawBlob = blobs.Slice((int)blobOffset, (int)blobSize);
|
||||
|
||||
ret[i] = new Shader
|
||||
{
|
||||
Stage = disassemble ? stage : DisassembledShader.ShaderStage.Unspecified,
|
||||
DirectXVersion = directX,
|
||||
Constants = ReadResourceArray(r, constantCount, strings),
|
||||
Samplers = ReadResourceArray(r, samplerCount, strings),
|
||||
Uavs = ReadResourceArray(r, uavCount, strings),
|
||||
AdditionalHeader = rawBlob[..extraHeaderSize].ToArray(),
|
||||
Blob = rawBlob[extraHeaderSize..].ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Key[] ReadKeyArray(BinaryReader r, int count)
|
||||
{
|
||||
var ret = new Key[count];
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
ret[i] = new Key
|
||||
{
|
||||
Id = r.ReadUInt32(),
|
||||
DefaultValue = r.ReadUInt32(),
|
||||
Values = Array.Empty<uint>(),
|
||||
};
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Node[] ReadNodeArray(BinaryReader r, int count, int systemKeyCount, int sceneKeyCount, int materialKeyCount,
|
||||
int subViewKeyCount)
|
||||
{
|
||||
var ret = new Node[count];
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var id = r.ReadUInt32();
|
||||
var passCount = r.ReadUInt32();
|
||||
ret[i] = new Node
|
||||
{
|
||||
Id = id,
|
||||
PassIndices = r.ReadBytes(16),
|
||||
SystemKeys = r.ReadStructuresAsArray<uint>(systemKeyCount),
|
||||
SceneKeys = r.ReadStructuresAsArray<uint>(sceneKeyCount),
|
||||
MaterialKeys = r.ReadStructuresAsArray<uint>(materialKeyCount),
|
||||
SubViewKeys = r.ReadStructuresAsArray<uint>(subViewKeyCount),
|
||||
Passes = r.ReadStructuresAsArray<Pass>((int)passCount),
|
||||
};
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public enum DxVersion : uint
|
||||
{
|
||||
DirectX9 = 9,
|
||||
DirectX11 = 11,
|
||||
}
|
||||
|
||||
public struct Resource
|
||||
{
|
||||
public uint Id;
|
||||
public string Name;
|
||||
public ushort Slot;
|
||||
public ushort Size;
|
||||
public DisassembledShader.VectorComponents[]? Used;
|
||||
public DisassembledShader.VectorComponents? UsedDynamically;
|
||||
}
|
||||
|
||||
public struct MaterialParam
|
||||
{
|
||||
public uint Id;
|
||||
public ushort ByteOffset;
|
||||
public ushort ByteSize;
|
||||
}
|
||||
|
||||
public struct Pass
|
||||
{
|
||||
public uint Id;
|
||||
public uint VertexShader;
|
||||
public uint PixelShader;
|
||||
}
|
||||
|
||||
public struct Key
|
||||
{
|
||||
public uint Id;
|
||||
public uint DefaultValue;
|
||||
public uint[] Values;
|
||||
}
|
||||
|
||||
public struct Node
|
||||
{
|
||||
public uint Id;
|
||||
public byte[] PassIndices;
|
||||
public uint[] SystemKeys;
|
||||
public uint[] SceneKeys;
|
||||
public uint[] MaterialKeys;
|
||||
public uint[] SubViewKeys;
|
||||
public Pass[] Passes;
|
||||
}
|
||||
|
||||
public struct Item
|
||||
{
|
||||
public uint Id;
|
||||
public uint Node;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using Lumina.Extensions;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public partial class StmFile
|
||||
{
|
||||
public readonly struct StainingTemplateEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of stains is capped at 128 at the moment
|
||||
/// </summary>
|
||||
public const int NumElements = 128;
|
||||
|
||||
// ColorSet row information for each stain.
|
||||
public readonly IReadOnlyList<(Half R, Half G, Half B)> DiffuseEntries;
|
||||
public readonly IReadOnlyList<(Half R, Half G, Half B)> SpecularEntries;
|
||||
public readonly IReadOnlyList<(Half R, Half G, Half B)> EmissiveEntries;
|
||||
public readonly IReadOnlyList<Half> GlossEntries;
|
||||
public readonly IReadOnlyList<Half> SpecularPowerEntries;
|
||||
|
||||
public DyePack this[StainId idx]
|
||||
=> this[(int)idx.Value];
|
||||
|
||||
public DyePack this[int idx]
|
||||
{
|
||||
get
|
||||
{
|
||||
// The 0th index is skipped.
|
||||
if (idx is <= 0 or > NumElements)
|
||||
return default;
|
||||
|
||||
--idx;
|
||||
var (dr, dg, db) = DiffuseEntries[idx];
|
||||
var (sr, sg, sb) = SpecularEntries[idx];
|
||||
var (er, eg, eb) = EmissiveEntries[idx];
|
||||
var g = GlossEntries[idx];
|
||||
var sp = SpecularPowerEntries[idx];
|
||||
// Convert to DyePack using floats.
|
||||
return new DyePack
|
||||
{
|
||||
Diffuse = new Vector3((float)dr, (float)dg, (float)db),
|
||||
Specular = new Vector3((float)sr, (float)sg, (float)sb),
|
||||
Emissive = new Vector3((float)er, (float)eg, (float)eb),
|
||||
Gloss = (float)g,
|
||||
SpecularPower = (float)sp,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<T> ReadArray<T>(BinaryReader br, int offset, int size, Func<BinaryReader, T> read, int entrySize)
|
||||
{
|
||||
br.Seek(offset);
|
||||
var arraySize = size / entrySize;
|
||||
// The actual amount of entries informs which type of list we use.
|
||||
switch (arraySize)
|
||||
{
|
||||
case 0: return new RepeatingList<T>(default!, NumElements); // All default
|
||||
case 1: return new RepeatingList<T>(read(br), NumElements); // All single entry
|
||||
case NumElements: // 1-to-1 entries
|
||||
var ret = new T[NumElements];
|
||||
for (var i = 0; i < NumElements; ++i)
|
||||
ret[i] = read(br);
|
||||
return ret;
|
||||
// Indexed access.
|
||||
case < NumElements: return new IndexedList<T>(br, arraySize - NumElements / entrySize, NumElements, read);
|
||||
// Should not happen.
|
||||
case > NumElements: throw new InvalidDataException($"Stain Template can not have more than {NumElements} elements.");
|
||||
}
|
||||
}
|
||||
|
||||
// Read functions
|
||||
private static (Half, Half, Half) ReadTriple(BinaryReader br)
|
||||
=> (br.ReadHalf(), br.ReadHalf(), br.ReadHalf());
|
||||
|
||||
private static Half ReadSingle(BinaryReader br)
|
||||
=> br.ReadHalf();
|
||||
|
||||
// Actually parse an entry.
|
||||
public unsafe StainingTemplateEntry(BinaryReader br, int offset)
|
||||
{
|
||||
br.Seek(offset);
|
||||
// 5 different lists of values.
|
||||
Span<ushort> ends = stackalloc ushort[5];
|
||||
for (var i = 0; i < ends.Length; ++i)
|
||||
ends[i] = (ushort)(br.ReadUInt16() * 2); // because the ends are in terms of ushort.
|
||||
offset += ends.Length * 2;
|
||||
|
||||
DiffuseEntries = ReadArray(br, offset, ends[0], ReadTriple, 6);
|
||||
SpecularEntries = ReadArray(br, offset + ends[0], ends[1] - ends[0], ReadTriple, 6);
|
||||
EmissiveEntries = ReadArray(br, offset + ends[1], ends[2] - ends[1], ReadTriple, 6);
|
||||
GlossEntries = ReadArray(br, offset + ends[2], ends[3] - ends[2], ReadSingle, 2);
|
||||
SpecularPowerEntries = ReadArray(br, offset + ends[3], ends[4] - ends[3], ReadSingle, 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used if a single value is used for all entries of a list.
|
||||
/// </summary>
|
||||
private sealed class RepeatingList<T> : IReadOnlyList<T>
|
||||
{
|
||||
private readonly T _value;
|
||||
public int Count { get; }
|
||||
|
||||
public RepeatingList(T value, int size)
|
||||
{
|
||||
_value = value;
|
||||
Count = size;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < Count; ++i)
|
||||
yield return _value;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public T this[int index]
|
||||
=> index >= 0 && index < Count ? _value : throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used if there is a small set of values for a bigger list, accessed via index information.
|
||||
/// </summary>
|
||||
private sealed class IndexedList<T> : IReadOnlyList<T>
|
||||
{
|
||||
private readonly T[] _values;
|
||||
private readonly byte[] _indices;
|
||||
|
||||
/// <summary>
|
||||
/// Reads <paramref name="count"/> values from <paramref name="br"/> via <paramref name="read"/>, then reads <paramref name="indexCount"/> byte indices.
|
||||
/// </summary>
|
||||
public IndexedList(BinaryReader br, int count, int indexCount, Func<BinaryReader, T> read)
|
||||
{
|
||||
_values = new T[count + 1];
|
||||
_indices = new byte[indexCount];
|
||||
_values[0] = default!;
|
||||
for (var i = 1; i < count + 1; ++i)
|
||||
_values[i] = read(br);
|
||||
|
||||
// Seems to be an unused 0xFF byte marker.
|
||||
// Necessary for correct offsets.
|
||||
br.ReadByte();
|
||||
for (var i = 0; i < indexCount; ++i)
|
||||
{
|
||||
_indices[i] = br.ReadByte();
|
||||
if (_indices[i] > count)
|
||||
_indices[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < NumElements; ++i)
|
||||
yield return _values[_indices[i]];
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public int Count
|
||||
=> _indices.Length;
|
||||
|
||||
public T this[int index]
|
||||
=> index >= 0 && index < Count ? _values[_indices[index]] : default!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using Dalamud.Data;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public partial class StmFile
|
||||
{
|
||||
public const string Path = "chara/base_material/stainingtemplate.stm";
|
||||
|
||||
/// <summary>
|
||||
/// All dye-able color set information for a row.
|
||||
/// </summary>
|
||||
public record struct DyePack
|
||||
{
|
||||
public Vector3 Diffuse;
|
||||
public Vector3 Specular;
|
||||
public Vector3 Emissive;
|
||||
public float Gloss;
|
||||
public float SpecularPower;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All currently available dyeing templates with their IDs.
|
||||
/// </summary>
|
||||
public readonly IReadOnlyDictionary<ushort, StainingTemplateEntry> Entries;
|
||||
|
||||
/// <summary>
|
||||
/// Access a specific dye pack.
|
||||
/// </summary>
|
||||
/// <param name="template">The ID of the accessed template.</param>
|
||||
/// <param name="idx">The ID of the Stain.</param>
|
||||
/// <returns>The corresponding color set information or a defaulted DyePack of 0-entries.</returns>
|
||||
public DyePack this[ushort template, int idx]
|
||||
=> Entries.TryGetValue(template, out var entry) ? entry[idx] : default;
|
||||
|
||||
/// <inheritdoc cref="this[ushort, StainId]"/>
|
||||
public DyePack this[ushort template, StainId idx]
|
||||
=> this[template, (int)idx.Value];
|
||||
|
||||
/// <summary>
|
||||
/// Try to access a specific dye pack.
|
||||
/// </summary>
|
||||
/// <param name="template">The ID of the accessed template.</param>
|
||||
/// <param name="idx">The ID of the Stain.</param>
|
||||
/// <param name="dyes">On success, the corresponding color set information, otherwise a defaulted DyePack.</param>
|
||||
/// <returns>True on success, false otherwise.</returns>
|
||||
public bool TryGetValue(ushort template, StainId idx, out DyePack dyes)
|
||||
{
|
||||
if (idx.Value is > 0 and <= StainingTemplateEntry.NumElements && Entries.TryGetValue(template, out var entry))
|
||||
{
|
||||
dyes = entry[idx];
|
||||
return true;
|
||||
}
|
||||
|
||||
dyes = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a STM file from the given data array.
|
||||
/// </summary>
|
||||
public StmFile(byte[] data)
|
||||
{
|
||||
using var stream = new MemoryStream(data);
|
||||
using var br = new BinaryReader(stream);
|
||||
br.ReadUInt32();
|
||||
var numEntries = br.ReadInt32();
|
||||
|
||||
var keys = new ushort[numEntries];
|
||||
var offsets = new ushort[numEntries];
|
||||
|
||||
for (var i = 0; i < numEntries; ++i)
|
||||
keys[i] = br.ReadUInt16();
|
||||
|
||||
for (var i = 0; i < numEntries; ++i)
|
||||
offsets[i] = br.ReadUInt16();
|
||||
|
||||
var entries = new Dictionary<ushort, StainingTemplateEntry>(numEntries);
|
||||
Entries = entries;
|
||||
|
||||
for (var i = 0; i < numEntries; ++i)
|
||||
{
|
||||
var offset = offsets[i] * 2 + 8 + 4 * numEntries;
|
||||
entries.Add(keys[i], new StainingTemplateEntry(br, offset));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to read and parse the default STM file given by Lumina.
|
||||
/// </summary>
|
||||
public StmFile(DataManager gameData)
|
||||
: this(gameData.GetFile(Path)?.Data ?? Array.Empty<byte>())
|
||||
{ }
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Plugin;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.GameData;
|
||||
|
||||
public static class GameData
|
||||
{
|
||||
/// <summary>
|
||||
/// Obtain an object identifier that can link a game path to game objects that use it, using your client language.
|
||||
/// </summary>
|
||||
public static IObjectIdentifier GetIdentifier(DalamudPluginInterface pluginInterface, DataManager dataManager, ItemData itemData)
|
||||
=> new ObjectIdentification(pluginInterface, dataManager, itemData, dataManager.Language);
|
||||
|
||||
/// <summary>
|
||||
/// Obtain an object identifier that can link a game path to game objects that use it using the given language.
|
||||
/// </summary>
|
||||
public static IObjectIdentifier GetIdentifier(DalamudPluginInterface pluginInterface, DataManager dataManager, ItemData itemData, ClientLanguage language)
|
||||
=> new ObjectIdentification(pluginInterface, dataManager, itemData, language);
|
||||
|
||||
/// <summary>
|
||||
/// Obtain a parser for game paths.
|
||||
/// </summary>
|
||||
public static IGamePathParser GetGamePathParser()
|
||||
=> new GamePathParser();
|
||||
}
|
||||
|
||||
public interface IObjectIdentifier : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// An accessible parser for game paths.
|
||||
/// </summary>
|
||||
public IGamePathParser GamePathParser { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Add all known game objects using the given game path to the dictionary.
|
||||
/// </summary>
|
||||
/// <param name="set">A pre-existing dictionary to which names (and optional linked objects) can be added.</param>
|
||||
/// <param name="path">The game path to identify.</param>
|
||||
public void Identify(IDictionary<string, object?> set, string path);
|
||||
|
||||
/// <summary>
|
||||
/// Return named information and possibly linked objects for all known game objects using the given path.
|
||||
/// </summary>
|
||||
/// <param name="path">The game path to identify.</param>
|
||||
public Dictionary<string, object?> Identify(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Identify an equippable item by its model values.
|
||||
/// </summary>
|
||||
/// <param name="setId">The primary model ID for the piece of equipment.</param>
|
||||
/// <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 IEnumerable<EquipItem> Identify(SetId setId, WeaponType weaponType, ushort variant, EquipSlot slot);
|
||||
|
||||
/// <inheritdoc cref="Identify(SetId, WeaponType, ushort, EquipSlot)"/>
|
||||
public IEnumerable<EquipItem> Identify(SetId setId, ushort variant, EquipSlot slot)
|
||||
=> Identify(setId, 0, variant, slot);
|
||||
|
||||
/// <summary> Obtain a list of BNPC Name Ids associated with a BNPC Id. </summary>
|
||||
public IReadOnlyList<uint> GetBnpcNames(uint bNpcId);
|
||||
|
||||
/// <summary> Obtain a list of Names and Object Kinds associated with a ModelChara ID. </summary>
|
||||
public IReadOnlyList<(string Name, ObjectKind Kind, uint Id)> ModelCharaNames(uint modelId);
|
||||
|
||||
public int NumModelChara { get; }
|
||||
}
|
||||
|
||||
public interface IGamePathParser
|
||||
{
|
||||
public ObjectType PathToObjectType(string path);
|
||||
public GameObjectInfo GetFileInfo(string path);
|
||||
public string VfxToKey(string path);
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Penumbra.GameData.Interop;
|
||||
|
||||
internal static class D3DCompiler
|
||||
{
|
||||
[Guid("8BA5FB08-5195-40e2-AC58-0D989C3A0102")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
private interface ID3DBlob
|
||||
{
|
||||
[PreserveSig]
|
||||
public unsafe void* GetBufferPointer();
|
||||
|
||||
[PreserveSig]
|
||||
public UIntPtr GetBufferSize();
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum DisassembleFlags : uint
|
||||
{
|
||||
EnableColorCode = 1,
|
||||
EnableDefaultValuePrints = 2,
|
||||
EnableInstructionNumbering = 4,
|
||||
EnableInstructionCycle = 8,
|
||||
DisableDebugInfo = 16,
|
||||
EnableInstructionOffset = 32,
|
||||
InstructionOnly = 64,
|
||||
PrintHexLiterals = 128,
|
||||
}
|
||||
|
||||
public static unsafe ByteString Disassemble(ReadOnlySpan<byte> blob, DisassembleFlags flags = 0, string comments = "")
|
||||
{
|
||||
ID3DBlob? disassembly = null;
|
||||
try
|
||||
{
|
||||
fixed (byte* pSrcData = blob)
|
||||
{
|
||||
var hr = D3DDisassemble(pSrcData, new UIntPtr((uint)blob.Length), (uint)flags, comments, out disassembly);
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
|
||||
return disassembly == null
|
||||
? ByteString.Empty
|
||||
: new ByteString((byte*)disassembly.GetBufferPointer()).Clone();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (disassembly != null)
|
||||
Marshal.FinalReleaseComObject(disassembly);
|
||||
}
|
||||
}
|
||||
|
||||
[PreserveSig]
|
||||
[DllImport("D3DCompiler_47.dll")]
|
||||
private static extern unsafe int D3DDisassemble(
|
||||
[In] byte* pSrcData,
|
||||
[In] UIntPtr srcDataSize,
|
||||
uint flags,
|
||||
[MarshalAs(UnmanagedType.LPStr)] string szComments,
|
||||
out ID3DBlob? ppDisassembly);
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
namespace Penumbra.GameData;
|
||||
|
||||
public static class Offsets
|
||||
{
|
||||
// ActorManager.Data
|
||||
public const int AgentCharaCardDataWorldId = 0xC0;
|
||||
|
||||
// ObjectIdentification
|
||||
public const int DrawObjectGetModelTypeVfunc = 50;
|
||||
private const int DrawObjectModelBase = 0x8E8;
|
||||
public const int DrawObjectModelUnk1 = DrawObjectModelBase;
|
||||
public const int DrawObjectModelUnk2 = DrawObjectModelBase + 0x08;
|
||||
public const int DrawObjectModelUnk3 = DrawObjectModelBase + 0x20;
|
||||
public const int DrawObjectModelUnk4 = DrawObjectModelBase + 0x28;
|
||||
|
||||
// PathResolver.AnimationState
|
||||
public const int GetGameObjectIdxVfunc = 28;
|
||||
public const int TimeLinePtr = 0x50;
|
||||
|
||||
// PathResolver.Meta
|
||||
public const int UpdateModelSkip = 0x90c;
|
||||
public const int GetEqpIndirectSkip1 = 0xA30;
|
||||
public const int GetEqpIndirectSkip2 = 0xA28;
|
||||
|
||||
// FontReloader
|
||||
public const int ReloadFontsVfunc = 43;
|
||||
|
||||
// ObjectReloader
|
||||
public const int EnableDrawVfunc = 16;
|
||||
public const int DisableDrawVfunc = 17;
|
||||
|
||||
// ResourceHandle
|
||||
public const int ResourceHandleGetDataVfunc = 23;
|
||||
public const int ResourceHandleGetLengthVfunc = 17;
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AssemblyTitle>Penumbra.GameData</AssemblyTitle>
|
||||
<Company>absolute gangstas</Company>
|
||||
<Product>Penumbra</Product>
|
||||
<Copyright>Copyright © 2022</Copyright>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Nullable>enable</Nullable>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugType>full</DebugType>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Penumbra.Api\Penumbra.Api.csproj" />
|
||||
<ProjectReference Include="..\Penumbra.String\Penumbra.String.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina.Excel">
|
||||
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFXIVClientStructs">
|
||||
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
namespace Penumbra.GameData;
|
||||
|
||||
public static class Sigs
|
||||
{
|
||||
// ResourceLoader.Debug
|
||||
public const string ResourceManager = "48 8B 05 ?? ?? ?? ?? 33 ED F0";
|
||||
|
||||
// ResourceLoader.Replacement
|
||||
public const string GetResourceSync = "E8 ?? ?? 00 00 48 8D 8F ?? ?? 00 00 48 89 87 ?? ?? 00 00";
|
||||
public const string GetResourceAsync = "E8 ?? ?? ?? 00 48 8B D8 EB ?? F0 FF 83 ?? ?? 00 00";
|
||||
public const string ReadFile = "E8 ?? ?? ?? ?? 84 C0 0F 84 ?? 00 00 00 4C 8B C3 BA 05";
|
||||
public const string ReadSqPack = "E8 ?? ?? ?? ?? EB 05 E8 ?? ?? ?? ?? 84 C0 0F 84 ?? 00 00 00 4C 8B C3";
|
||||
|
||||
// ResourceLoader.TexMdl
|
||||
public const string CheckFileState = "E8 ?? ?? ?? ?? 48 85 c0 74 ?? 45 0f b6 ce 48 89 44 24";
|
||||
public const string LoadTexFileLocal = "48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 48 83 EC 30 49 8B F0 44 88 4C 24 20";
|
||||
public const string LoadMdlFileLocal = "40 55 53 56 57 41 56 41 57 48 8D 6C 24 D1 48 81 EC 98 00 00 00";
|
||||
public const string LoadTexFileExtern = "E8 ?? ?? ?? ?? 0F B6 E8 48 8B CB E8";
|
||||
public const string LoadMdlFileExtern = "E8 ?? ?? ?? ?? EB 02 B0 F1";
|
||||
|
||||
// GameEventManager
|
||||
public const string ResourceHandleDestructor = "48 89 5C 24 ?? 57 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 48 8B D9 48 89 01 B8";
|
||||
public const string CopyCharacter = "E8 ?? ?? ?? ?? 0F B6 9F ?? ?? ?? ?? 48 8D 8F";
|
||||
|
||||
public const string CharacterDestructor =
|
||||
"48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 48 8B D9 48 89 01 48 8D 05 ?? ?? ?? ?? 48 89 81 ?? ?? ?? ?? 48 8D 05";
|
||||
|
||||
// PathResolver.AnimationState
|
||||
public const string LoadCharacterSound = "4C 89 4C 24 ?? 55 57 41 56";
|
||||
public const string LoadTimelineResources = "E8 ?? ?? ?? ?? 83 7F ?? ?? 75 ?? 0F B6 87";
|
||||
public const string CharacterBaseLoadAnimation = "E8 ?? ?? ?? ?? BA ?? ?? ?? ?? 48 8B CF 44 8B C2 E8 ?? ?? ?? ?? 48 8B 05";
|
||||
public const string LoadSomePap = "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 41 8B D9 89 51";
|
||||
public const string LoadSomeAction = "E8 ?? ?? ?? ?? C6 83 ?? ?? ?? ?? ?? 8B 8E";
|
||||
public const string LoadCharacterVfx = "E8 ?? ?? ?? ?? 48 8B F8 48 8D 93";
|
||||
public const string LoadAreaVfx = "48 8B C4 53 55 56 57 41 56 48 81 EC";
|
||||
public const string ScheduleClipUpdate = "40 53 55 56 57 41 56 48 81 EC ?? ?? ?? ?? 48 8B F9";
|
||||
|
||||
// PathResolver.DrawObjectState
|
||||
public const string CharacterBaseCreate = "E8 ?? ?? ?? ?? 48 85 C0 74 21 C7 40";
|
||||
|
||||
public const string CharacterBaseDestructor =
|
||||
"E8 ?? ?? ?? ?? 40 F6 C7 01 74 3A 40 F6 C7 04 75 27 48 85 DB 74 2F 48 8B 05 ?? ?? ?? ?? 48 8B D3 48 8B 48 30";
|
||||
|
||||
public const string EnableDraw = "E8 ?? ?? ?? ?? 48 8B 8B ?? ?? ?? ?? 48 85 C9 74 33 45 33 C0";
|
||||
public const string WeaponReload = "E8 ?? ?? ?? ?? 33 DB BE";
|
||||
|
||||
// PathResolver.Meta
|
||||
public const string UpdateModel = "48 8B ?? 56 48 83 ?? ?? ?? B9";
|
||||
public const string GetEqpIndirect = "40 ?? 48 83 ?? ?? ?? 81 ?? ?? ?? ?? ?? 48 8B ?? 74 ?? ?? 83 ?? ?? ?? ?? ?? ?? 74 ?? 4C";
|
||||
public const string SetupVisor = "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B";
|
||||
public const string RspSetupCharacter = "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 88 54 24 ?? 57 41 56";
|
||||
public const string ChangeCustomize = "E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86";
|
||||
|
||||
// PathResolver.PathState
|
||||
public const string HumanVTable = "48 8D 05 ?? ?? ?? ?? 48 89 03 48 B8 ?? ?? ?? ?? ?? ?? ?? ?? 89 8B";
|
||||
|
||||
public const string WeaponVTable =
|
||||
"48 8D 05 ?? ?? ?? ?? 48 89 03 B8 ?? ?? ?? ?? 66 89 83 ?? ?? ?? ?? 48 8B C3 48 89 8B ?? ?? ?? ?? 48 89 8B";
|
||||
|
||||
public const string DemiHumanVTable = "48 8D 05 ?? ?? ?? ?? 48 89 03 48 8B C3 89 8B";
|
||||
public const string MonsterVTable = "48 8D 05 ?? ?? ?? ?? 48 89 03 33 C0 48 89 83 ?? ?? ?? ?? 48 89 83 ?? ?? ?? ?? C7 83";
|
||||
|
||||
// PathResolver.Subfiles
|
||||
public const string LoadMtrlTex = "4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 55";
|
||||
|
||||
public const string LoadMtrlShpk =
|
||||
"48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B7 89";
|
||||
|
||||
public const string ApricotResourceLoad = "48 89 74 24 ?? 57 48 83 EC ?? 41 0F B6 F0 48 8B F9";
|
||||
|
||||
// CharacterUtility
|
||||
public const string CharacterUtility = "48 8B 05 ?? ?? ?? ?? 83 B9";
|
||||
public const string LoadCharacterResources = "E8 ?? ?? ?? ?? 48 8D 8F ?? ?? ?? ?? E8 ?? ?? ?? ?? 33 D2 45 33 C0";
|
||||
|
||||
// MetaFileManager
|
||||
public const string GetFileSpace = "E8 ?? ?? ?? ?? 41 B9 ?? ?? ?? ?? 4C 8B C0";
|
||||
|
||||
// ResidentResourceManager
|
||||
public const string ResidentResourceManager = "0F 44 FE 48 8B 0D ?? ?? ?? ?? 48 85 C9 74 05";
|
||||
|
||||
public const string LoadPlayerResources =
|
||||
"E8 ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? BA ?? ?? ?? ?? 41 B8 ?? ?? ?? ?? 48 8B 48 30 48 8B 01 FF 50 10 48 85 C0 74 0A";
|
||||
|
||||
public const string UnloadPlayerResources =
|
||||
"41 55 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 4C 8B E9 48 83 C1 08";
|
||||
|
||||
// ActorManager
|
||||
public const string InspectTitleId = "0F B7 0D ?? ?? ?? ?? C7 85";
|
||||
public const string InspectWorldId = "0F B7 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 8B D0";
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Pack = 1)]
|
||||
public struct CharacterArmor : IEquatable<CharacterArmor>
|
||||
{
|
||||
public const int Size = 4;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public readonly uint Value;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public SetId Set;
|
||||
|
||||
[FieldOffset(2)]
|
||||
public byte Variant;
|
||||
|
||||
[FieldOffset(3)]
|
||||
public StainId Stain;
|
||||
|
||||
public CharacterArmor(SetId set, byte variant, StainId stain)
|
||||
{
|
||||
Value = 0;
|
||||
Set = set;
|
||||
Variant = variant;
|
||||
Stain = stain;
|
||||
}
|
||||
|
||||
public readonly CharacterArmor With(StainId stain)
|
||||
=> new(Set, Variant, stain);
|
||||
|
||||
public readonly CharacterWeapon ToWeapon(WeaponType type)
|
||||
=> new(Set, type, Variant, Stain);
|
||||
|
||||
public readonly CharacterWeapon ToWeapon(WeaponType type, StainId stain)
|
||||
=> new(Set, type, Variant, stain);
|
||||
|
||||
public override readonly string ToString()
|
||||
=> $"{Set},{Variant},{Stain}";
|
||||
|
||||
public static readonly CharacterArmor Empty;
|
||||
|
||||
public readonly bool Equals(CharacterArmor other)
|
||||
=> Value == other.Value;
|
||||
|
||||
public override readonly bool Equals(object? obj)
|
||||
=> obj is CharacterArmor other && Equals(other);
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
=> (int)Value;
|
||||
|
||||
public static bool operator ==(CharacterArmor left, CharacterArmor right)
|
||||
=> left.Value == right.Value;
|
||||
|
||||
public static bool operator !=(CharacterArmor left, CharacterArmor right)
|
||||
=> left.Value != right.Value;
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 7)]
|
||||
public struct CharacterWeapon : IEquatable<CharacterWeapon>
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public SetId Set;
|
||||
|
||||
[FieldOffset(2)]
|
||||
public WeaponType Type;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public ushort Variant;
|
||||
|
||||
[FieldOffset(6)]
|
||||
public StainId Stain;
|
||||
|
||||
public readonly ulong Value
|
||||
=> (ulong)Set | ((ulong)Type << 16) | ((ulong)Variant << 32) | ((ulong)Stain << 48);
|
||||
|
||||
public override readonly string ToString()
|
||||
=> $"{Set},{Type},{Variant},{Stain}";
|
||||
|
||||
public CharacterWeapon(SetId set, WeaponType type, ushort variant, StainId stain)
|
||||
{
|
||||
Set = set;
|
||||
Type = type;
|
||||
Variant = variant;
|
||||
Stain = stain;
|
||||
}
|
||||
|
||||
public CharacterWeapon(ulong value)
|
||||
{
|
||||
Set = (SetId)value;
|
||||
Type = (WeaponType)(value >> 16);
|
||||
Variant = (ushort)(value >> 32);
|
||||
Stain = (StainId)(value >> 48);
|
||||
}
|
||||
|
||||
public readonly CharacterWeapon With(StainId stain)
|
||||
=> new(Set, Type, Variant, stain);
|
||||
|
||||
public readonly CharacterArmor ToArmor()
|
||||
=> new(Set, (byte)Variant, Stain);
|
||||
|
||||
public readonly CharacterArmor ToArmor(StainId stain)
|
||||
=> new(Set, (byte)Variant, stain);
|
||||
|
||||
public static readonly CharacterWeapon Empty = new(0, 0, 0, 0);
|
||||
|
||||
public readonly bool Equals(CharacterWeapon other)
|
||||
=> Value == other.Value;
|
||||
|
||||
public override readonly bool Equals(object? obj)
|
||||
=> obj is CharacterWeapon other && Equals(other);
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
=> Value.GetHashCode();
|
||||
|
||||
public static bool operator ==(CharacterWeapon left, CharacterWeapon right)
|
||||
=> left.Value == right.Value;
|
||||
|
||||
public static bool operator !=(CharacterWeapon left, CharacterWeapon right)
|
||||
=> left.Value != right.Value;
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Penumbra.String.Functions;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size)]
|
||||
public unsafe struct CustomizeData : IEquatable<CustomizeData>, IReadOnlyCollection<byte>
|
||||
{
|
||||
public const int Size = 26;
|
||||
|
||||
public fixed byte Data[Size];
|
||||
|
||||
public int Count
|
||||
=> Size;
|
||||
|
||||
public IEnumerator<byte> GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < Size; ++i)
|
||||
yield return At(i);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
|
||||
public byte At(int index)
|
||||
=> Data[index];
|
||||
|
||||
public void Set(int index, byte value)
|
||||
=> Data[index] = value;
|
||||
|
||||
public void Read(void* source)
|
||||
{
|
||||
fixed (byte* ptr = Data)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked(ptr, source, Size);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly void Write(void* target)
|
||||
{
|
||||
fixed (byte* ptr = Data)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked(target, ptr, Size);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly CustomizeData Clone()
|
||||
{
|
||||
var ret = new CustomizeData();
|
||||
Write(ret.Data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public readonly bool Equals(CustomizeData other)
|
||||
{
|
||||
fixed (byte* ptr = Data)
|
||||
{
|
||||
return MemoryUtility.MemCmpUnchecked(ptr, other.Data, Size) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is CustomizeData other && Equals(other);
|
||||
|
||||
public static bool Equals(CustomizeData* lhs, CustomizeData* rhs)
|
||||
=> MemoryUtility.MemCmpUnchecked(lhs, rhs, Size) == 0;
|
||||
|
||||
/// <remarks>Compare Gender and then only from Height onwards, because all screen actors are set to Height 50,
|
||||
/// the Race is implicitly included in the subrace (after height),
|
||||
/// and the body type is irrelevant for players.</remarks>>
|
||||
public static bool ScreenActorEquals(CustomizeData* lhs, CustomizeData* rhs)
|
||||
=> lhs->Data[1] == rhs->Data[1] && MemoryUtility.MemCmpUnchecked(lhs->Data + 4, rhs->Data + 4, Size - 4) == 0;
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
fixed (byte* ptr = Data)
|
||||
{
|
||||
var p = (int*)ptr;
|
||||
var u = *(ushort*)(p + 6);
|
||||
return HashCode.Combine(*p, p[1], p[2], p[3], p[4], p[5], u);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly string WriteBase64()
|
||||
{
|
||||
fixed (byte* ptr = Data)
|
||||
{
|
||||
var data = new ReadOnlySpan<byte>(ptr, Size);
|
||||
return Convert.ToBase64String(data);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder(Size * 3);
|
||||
for (var i = 0; i < Size - 1; ++i)
|
||||
sb.Append($"{Data[i]:X2} ");
|
||||
sb.Append($"{Data[Size - 1]:X2}");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
public bool LoadBase64(string base64)
|
||||
{
|
||||
var buffer = stackalloc byte[Size];
|
||||
var span = new Span<byte>(buffer, Size);
|
||||
if (!Convert.TryFromBase64String(base64, span, out var written) || written != Size)
|
||||
return false;
|
||||
|
||||
Read(buffer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
[Flags]
|
||||
public enum EqdpEntry : ushort
|
||||
{
|
||||
Invalid = 0,
|
||||
Head1 = 0b0000000001,
|
||||
Head2 = 0b0000000010,
|
||||
HeadMask = 0b0000000011,
|
||||
|
||||
Body1 = 0b0000000100,
|
||||
Body2 = 0b0000001000,
|
||||
BodyMask = 0b0000001100,
|
||||
|
||||
Hands1 = 0b0000010000,
|
||||
Hands2 = 0b0000100000,
|
||||
HandsMask = 0b0000110000,
|
||||
|
||||
Legs1 = 0b0001000000,
|
||||
Legs2 = 0b0010000000,
|
||||
LegsMask = 0b0011000000,
|
||||
|
||||
Feet1 = 0b0100000000,
|
||||
Feet2 = 0b1000000000,
|
||||
FeetMask = 0b1100000000,
|
||||
|
||||
Ears1 = 0b0000000001,
|
||||
Ears2 = 0b0000000010,
|
||||
EarsMask = 0b0000000011,
|
||||
|
||||
Neck1 = 0b0000000100,
|
||||
Neck2 = 0b0000001000,
|
||||
NeckMask = 0b0000001100,
|
||||
|
||||
Wrists1 = 0b0000010000,
|
||||
Wrists2 = 0b0000100000,
|
||||
WristsMask = 0b0000110000,
|
||||
|
||||
RingR1 = 0b0001000000,
|
||||
RingR2 = 0b0010000000,
|
||||
RingRMask = 0b0011000000,
|
||||
|
||||
RingL1 = 0b0100000000,
|
||||
RingL2 = 0b1000000000,
|
||||
RingLMask = 0b1100000000,
|
||||
}
|
||||
|
||||
public static class Eqdp
|
||||
{
|
||||
public static int Offset( EquipSlot slot )
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.Head => 0,
|
||||
EquipSlot.Body => 2,
|
||||
EquipSlot.Hands => 4,
|
||||
EquipSlot.Legs => 6,
|
||||
EquipSlot.Feet => 8,
|
||||
EquipSlot.Ears => 0,
|
||||
EquipSlot.Neck => 2,
|
||||
EquipSlot.Wrists => 4,
|
||||
EquipSlot.RFinger => 6,
|
||||
EquipSlot.LFinger => 8,
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
|
||||
public static (bool, bool) ToBits( this EqdpEntry entry, EquipSlot slot )
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.Head => ( entry.HasFlag( EqdpEntry.Head1 ), entry.HasFlag( EqdpEntry.Head2 ) ),
|
||||
EquipSlot.Body => ( entry.HasFlag( EqdpEntry.Body1 ), entry.HasFlag( EqdpEntry.Body2 ) ),
|
||||
EquipSlot.Hands => ( entry.HasFlag( EqdpEntry.Hands1 ), entry.HasFlag( EqdpEntry.Hands2 ) ),
|
||||
EquipSlot.Legs => ( entry.HasFlag( EqdpEntry.Legs1 ), entry.HasFlag( EqdpEntry.Legs2 ) ),
|
||||
EquipSlot.Feet => ( entry.HasFlag( EqdpEntry.Feet1 ), entry.HasFlag( EqdpEntry.Feet2 ) ),
|
||||
EquipSlot.Ears => ( entry.HasFlag( EqdpEntry.Ears1 ), entry.HasFlag( EqdpEntry.Ears2 ) ),
|
||||
EquipSlot.Neck => ( entry.HasFlag( EqdpEntry.Neck1 ), entry.HasFlag( EqdpEntry.Neck2 ) ),
|
||||
EquipSlot.Wrists => ( entry.HasFlag( EqdpEntry.Wrists1 ), entry.HasFlag( EqdpEntry.Wrists2 ) ),
|
||||
EquipSlot.RFinger => ( entry.HasFlag( EqdpEntry.RingR1 ), entry.HasFlag( EqdpEntry.RingR2 ) ),
|
||||
EquipSlot.LFinger => ( entry.HasFlag( EqdpEntry.RingL1 ), entry.HasFlag( EqdpEntry.RingL2 ) ),
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
|
||||
public static EqdpEntry FromSlotAndBits( EquipSlot slot, bool bit1, bool bit2 )
|
||||
{
|
||||
EqdpEntry ret = 0;
|
||||
var offset = Offset( slot );
|
||||
if( bit1 )
|
||||
{
|
||||
ret |= ( EqdpEntry )( 1 << offset );
|
||||
}
|
||||
|
||||
if( bit2 )
|
||||
{
|
||||
ret |= ( EqdpEntry )( 1 << ( offset + 1 ) );
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static EqdpEntry Mask( EquipSlot slot )
|
||||
{
|
||||
return slot switch
|
||||
{
|
||||
EquipSlot.Head => EqdpEntry.HeadMask,
|
||||
EquipSlot.Body => EqdpEntry.BodyMask,
|
||||
EquipSlot.Hands => EqdpEntry.HandsMask,
|
||||
EquipSlot.Legs => EqdpEntry.LegsMask,
|
||||
EquipSlot.Feet => EqdpEntry.FeetMask,
|
||||
EquipSlot.Ears => EqdpEntry.EarsMask,
|
||||
EquipSlot.Neck => EqdpEntry.NeckMask,
|
||||
EquipSlot.Wrists => EqdpEntry.WristsMask,
|
||||
EquipSlot.RFinger => EqdpEntry.RingRMask,
|
||||
EquipSlot.LFinger => EqdpEntry.RingLMask,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,316 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.ComponentModel;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
[Flags]
|
||||
public enum EqpEntry : ulong
|
||||
{
|
||||
BodyEnabled = 0x00_01ul,
|
||||
BodyHideWaist = 0x00_02ul,
|
||||
BodyHideThighs = 0x00_04ul,
|
||||
BodyHideGlovesS = 0x00_08ul,
|
||||
_4 = 0x00_10ul,
|
||||
BodyHideGlovesM = 0x00_20ul,
|
||||
BodyHideGlovesL = 0x00_40ul,
|
||||
BodyHideGorget = 0x00_80ul,
|
||||
BodyShowLeg = 0x01_00ul,
|
||||
BodyShowHand = 0x02_00ul,
|
||||
BodyShowHead = 0x04_00ul,
|
||||
BodyShowNecklace = 0x08_00ul,
|
||||
BodyShowBracelet = 0x10_00ul,
|
||||
BodyShowTail = 0x20_00ul,
|
||||
BodyDisableBreastPhysics = 0x40_00ul,
|
||||
BodyUsesEvpTable = 0x80_00ul,
|
||||
BodyMask = 0xFF_FFul,
|
||||
|
||||
LegsEnabled = 0x01ul << 16,
|
||||
LegsHideKneePads = 0x02ul << 16,
|
||||
LegsHideBootsS = 0x04ul << 16,
|
||||
LegsHideBootsM = 0x08ul << 16,
|
||||
_20 = 0x10ul << 16,
|
||||
LegsShowFoot = 0x20ul << 16,
|
||||
LegsShowTail = 0x40ul << 16,
|
||||
_23 = 0x80ul << 16,
|
||||
LegsMask = 0xFFul << 16,
|
||||
|
||||
HandsEnabled = 0x01ul << 24,
|
||||
HandsHideElbow = 0x02ul << 24,
|
||||
HandsHideForearm = 0x04ul << 24,
|
||||
_27 = 0x08ul << 24,
|
||||
HandShowBracelet = 0x10ul << 24,
|
||||
HandShowRingL = 0x20ul << 24,
|
||||
HandShowRingR = 0x40ul << 24,
|
||||
_31 = 0x80ul << 24,
|
||||
HandsMask = 0xFFul << 24,
|
||||
|
||||
FeetEnabled = 0x01ul << 32,
|
||||
FeetHideKnee = 0x02ul << 32,
|
||||
FeetHideCalf = 0x04ul << 32,
|
||||
FeetHideAnkle = 0x08ul << 32,
|
||||
_36 = 0x10ul << 32,
|
||||
_37 = 0x20ul << 32,
|
||||
_38 = 0x40ul << 32,
|
||||
_39 = 0x80ul << 32,
|
||||
FeetMask = 0xFFul << 32,
|
||||
|
||||
HeadEnabled = 0x00_00_01ul << 40,
|
||||
HeadHideScalp = 0x00_00_02ul << 40,
|
||||
HeadHideHair = 0x00_00_04ul << 40,
|
||||
HeadShowHairOverride = 0x00_00_08ul << 40,
|
||||
HeadHideNeck = 0x00_00_10ul << 40,
|
||||
HeadShowNecklace = 0x00_00_20ul << 40,
|
||||
_46 = 0x00_00_40ul << 40,
|
||||
HeadShowEarrings = 0x00_00_80ul << 40,
|
||||
HeadShowEarringsHuman = 0x00_01_00ul << 40,
|
||||
HeadShowEarringsAura = 0x00_02_00ul << 40,
|
||||
HeadShowEarHuman = 0x00_04_00ul << 40,
|
||||
HeadShowEarMiqote = 0x00_08_00ul << 40,
|
||||
HeadShowEarAuRa = 0x00_10_00ul << 40,
|
||||
HeadShowEarViera = 0x00_20_00ul << 40,
|
||||
_54 = 0x00_40_00ul << 40,
|
||||
_55 = 0x00_80_00ul << 40,
|
||||
HeadShowHrothgarHat = 0x01_00_00ul << 40,
|
||||
HeadShowVieraHat = 0x02_00_00ul << 40,
|
||||
HeadUsesEvpTable = 0x04_00_00ul << 40,
|
||||
_59 = 0x08_00_00ul << 40,
|
||||
_60 = 0x10_00_00ul << 40,
|
||||
_61 = 0x20_00_00ul << 40,
|
||||
_62 = 0x40_00_00ul << 40,
|
||||
_63 = 0x80_00_00ul << 40,
|
||||
HeadMask = 0xFF_FF_FFul << 40,
|
||||
}
|
||||
|
||||
public static class Eqp
|
||||
{
|
||||
// cf. Client::Graphics::Scene::CharacterUtility.GetSlotEqpFlags
|
||||
public const EqpEntry DefaultEntry = (EqpEntry)0x3fe00070603f00;
|
||||
|
||||
public static (int, int) BytesAndOffset(EquipSlot slot)
|
||||
{
|
||||
return slot switch
|
||||
{
|
||||
EquipSlot.Body => (2, 0),
|
||||
EquipSlot.Legs => (1, 2),
|
||||
EquipSlot.Hands => (1, 3),
|
||||
EquipSlot.Feet => (1, 4),
|
||||
EquipSlot.Head => (3, 5),
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static EqpEntry ShiftAndMask(this EqpEntry entry, EquipSlot slot)
|
||||
{
|
||||
var (_, offset) = BytesAndOffset(slot);
|
||||
var mask = Mask(slot);
|
||||
return (EqpEntry)((ulong)(entry & mask) >> (offset * 8));
|
||||
}
|
||||
|
||||
public static EqpEntry FromSlotAndBytes(EquipSlot slot, byte[] value)
|
||||
{
|
||||
EqpEntry ret = 0;
|
||||
var (bytes, offset) = BytesAndOffset(slot);
|
||||
if (bytes != value.Length)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (var i = 0; i < bytes; ++i)
|
||||
ret |= (EqpEntry)((ulong)value[i] << ((offset + i) * 8));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static EqpEntry Mask(EquipSlot slot)
|
||||
{
|
||||
return slot switch
|
||||
{
|
||||
EquipSlot.Body => EqpEntry.BodyMask,
|
||||
EquipSlot.Head => EqpEntry.HeadMask,
|
||||
EquipSlot.Legs => EqpEntry.LegsMask,
|
||||
EquipSlot.Feet => EqpEntry.FeetMask,
|
||||
EquipSlot.Hands => EqpEntry.HandsMask,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
public static EquipSlot ToEquipSlot(this EqpEntry entry)
|
||||
{
|
||||
return entry switch
|
||||
{
|
||||
EqpEntry.BodyEnabled => EquipSlot.Body,
|
||||
EqpEntry.BodyHideWaist => EquipSlot.Body,
|
||||
EqpEntry.BodyHideThighs => EquipSlot.Body,
|
||||
EqpEntry.BodyHideGlovesS => EquipSlot.Body,
|
||||
EqpEntry._4 => EquipSlot.Body,
|
||||
EqpEntry.BodyHideGlovesM => EquipSlot.Body,
|
||||
EqpEntry.BodyHideGlovesL => EquipSlot.Body,
|
||||
EqpEntry.BodyHideGorget => EquipSlot.Body,
|
||||
EqpEntry.BodyShowLeg => EquipSlot.Body,
|
||||
EqpEntry.BodyShowHand => EquipSlot.Body,
|
||||
EqpEntry.BodyShowHead => EquipSlot.Body,
|
||||
EqpEntry.BodyShowNecklace => EquipSlot.Body,
|
||||
EqpEntry.BodyShowBracelet => EquipSlot.Body,
|
||||
EqpEntry.BodyShowTail => EquipSlot.Body,
|
||||
EqpEntry.BodyDisableBreastPhysics => EquipSlot.Body,
|
||||
EqpEntry.BodyUsesEvpTable => EquipSlot.Body,
|
||||
|
||||
EqpEntry.LegsEnabled => EquipSlot.Legs,
|
||||
EqpEntry.LegsHideKneePads => EquipSlot.Legs,
|
||||
EqpEntry.LegsHideBootsS => EquipSlot.Legs,
|
||||
EqpEntry.LegsHideBootsM => EquipSlot.Legs,
|
||||
EqpEntry._20 => EquipSlot.Legs,
|
||||
EqpEntry.LegsShowFoot => EquipSlot.Legs,
|
||||
EqpEntry.LegsShowTail => EquipSlot.Legs,
|
||||
EqpEntry._23 => EquipSlot.Legs,
|
||||
|
||||
EqpEntry.HandsEnabled => EquipSlot.Hands,
|
||||
EqpEntry.HandsHideElbow => EquipSlot.Hands,
|
||||
EqpEntry.HandsHideForearm => EquipSlot.Hands,
|
||||
EqpEntry._27 => EquipSlot.Hands,
|
||||
EqpEntry.HandShowBracelet => EquipSlot.Hands,
|
||||
EqpEntry.HandShowRingL => EquipSlot.Hands,
|
||||
EqpEntry.HandShowRingR => EquipSlot.Hands,
|
||||
EqpEntry._31 => EquipSlot.Hands,
|
||||
|
||||
EqpEntry.FeetEnabled => EquipSlot.Feet,
|
||||
EqpEntry.FeetHideKnee => EquipSlot.Feet,
|
||||
EqpEntry.FeetHideCalf => EquipSlot.Feet,
|
||||
EqpEntry.FeetHideAnkle => EquipSlot.Feet,
|
||||
EqpEntry._36 => EquipSlot.Feet,
|
||||
EqpEntry._37 => EquipSlot.Feet,
|
||||
EqpEntry._38 => EquipSlot.Feet,
|
||||
EqpEntry._39 => EquipSlot.Feet,
|
||||
|
||||
EqpEntry.HeadEnabled => EquipSlot.Head,
|
||||
EqpEntry.HeadHideScalp => EquipSlot.Head,
|
||||
EqpEntry.HeadHideHair => EquipSlot.Head,
|
||||
EqpEntry.HeadShowHairOverride => EquipSlot.Head,
|
||||
EqpEntry.HeadHideNeck => EquipSlot.Head,
|
||||
EqpEntry.HeadShowNecklace => EquipSlot.Head,
|
||||
EqpEntry._46 => EquipSlot.Head,
|
||||
EqpEntry.HeadShowEarrings => EquipSlot.Head,
|
||||
EqpEntry.HeadShowEarringsHuman => EquipSlot.Head,
|
||||
EqpEntry.HeadShowEarringsAura => EquipSlot.Head,
|
||||
EqpEntry.HeadShowEarHuman => EquipSlot.Head,
|
||||
EqpEntry.HeadShowEarMiqote => EquipSlot.Head,
|
||||
EqpEntry.HeadShowEarAuRa => EquipSlot.Head,
|
||||
EqpEntry.HeadShowEarViera => EquipSlot.Head,
|
||||
EqpEntry._54 => EquipSlot.Head,
|
||||
EqpEntry._55 => EquipSlot.Head,
|
||||
EqpEntry.HeadShowHrothgarHat => EquipSlot.Head,
|
||||
EqpEntry.HeadShowVieraHat => EquipSlot.Head,
|
||||
EqpEntry.HeadUsesEvpTable => EquipSlot.Head,
|
||||
|
||||
// currently unused
|
||||
EqpEntry._59 => EquipSlot.Unknown,
|
||||
EqpEntry._60 => EquipSlot.Unknown,
|
||||
EqpEntry._61 => EquipSlot.Unknown,
|
||||
EqpEntry._62 => EquipSlot.Unknown,
|
||||
EqpEntry._63 => EquipSlot.Unknown,
|
||||
|
||||
_ => EquipSlot.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToLocalName(this EqpEntry entry)
|
||||
{
|
||||
return entry switch
|
||||
{
|
||||
EqpEntry.BodyEnabled => "Enabled",
|
||||
EqpEntry.BodyHideWaist => "Hide Waist",
|
||||
EqpEntry.BodyHideThighs => "Hide Thigh Pads",
|
||||
EqpEntry.BodyHideGlovesS => "Hide Small Gloves",
|
||||
EqpEntry._4 => "Unknown 4",
|
||||
EqpEntry.BodyHideGlovesM => "Hide Medium Gloves",
|
||||
EqpEntry.BodyHideGlovesL => "Hide Large Gloves",
|
||||
EqpEntry.BodyHideGorget => "Hide Gorget",
|
||||
EqpEntry.BodyShowLeg => "Show Legs",
|
||||
EqpEntry.BodyShowHand => "Show Hands",
|
||||
EqpEntry.BodyShowHead => "Show Head",
|
||||
EqpEntry.BodyShowNecklace => "Show Necklace",
|
||||
EqpEntry.BodyShowBracelet => "Show Bracelet",
|
||||
EqpEntry.BodyShowTail => "Show Tail",
|
||||
EqpEntry.BodyDisableBreastPhysics => "Disable Breast Physics",
|
||||
EqpEntry.BodyUsesEvpTable => "Uses EVP Table",
|
||||
|
||||
EqpEntry.LegsEnabled => "Enabled",
|
||||
EqpEntry.LegsHideKneePads => "Hide Knee Pads",
|
||||
EqpEntry.LegsHideBootsS => "Hide Small Boots",
|
||||
EqpEntry.LegsHideBootsM => "Hide Medium Boots",
|
||||
EqpEntry._20 => "Unknown 20",
|
||||
EqpEntry.LegsShowFoot => "Show Foot",
|
||||
EqpEntry.LegsShowTail => "Show Tail",
|
||||
EqpEntry._23 => "Unknown 23",
|
||||
|
||||
EqpEntry.HandsEnabled => "Enabled",
|
||||
EqpEntry.HandsHideElbow => "Hide Elbow",
|
||||
EqpEntry.HandsHideForearm => "Hide Forearm",
|
||||
EqpEntry._27 => "Unknown 27",
|
||||
EqpEntry.HandShowBracelet => "Show Bracelet",
|
||||
EqpEntry.HandShowRingL => "Show Left Ring",
|
||||
EqpEntry.HandShowRingR => "Show Right Ring",
|
||||
EqpEntry._31 => "Unknown 31",
|
||||
|
||||
EqpEntry.FeetEnabled => "Enabled",
|
||||
EqpEntry.FeetHideKnee => "Hide Knees",
|
||||
EqpEntry.FeetHideCalf => "Hide Calves",
|
||||
EqpEntry.FeetHideAnkle => "Hide Ankles",
|
||||
EqpEntry._36 => "Unknown 36",
|
||||
EqpEntry._37 => "Unknown 37",
|
||||
EqpEntry._38 => "Unknown 38",
|
||||
EqpEntry._39 => "Unknown 39",
|
||||
|
||||
EqpEntry.HeadEnabled => "Enabled",
|
||||
EqpEntry.HeadHideScalp => "Hide Scalp",
|
||||
EqpEntry.HeadHideHair => "Hide Hair",
|
||||
EqpEntry.HeadShowHairOverride => "Show Hair Override",
|
||||
EqpEntry.HeadHideNeck => "Hide Neck",
|
||||
EqpEntry.HeadShowNecklace => "Show Necklace",
|
||||
EqpEntry._46 => "Unknown 46",
|
||||
EqpEntry.HeadShowEarrings => "Show Earrings",
|
||||
EqpEntry.HeadShowEarringsHuman => "Show Earrings (Human)",
|
||||
EqpEntry.HeadShowEarringsAura => "Show Earrings (Au Ra)",
|
||||
EqpEntry.HeadShowEarHuman => "Show Ears (Human)",
|
||||
EqpEntry.HeadShowEarMiqote => "Show Ears (Miqo'te)",
|
||||
EqpEntry.HeadShowEarAuRa => "Show Ears (Au Ra)",
|
||||
EqpEntry.HeadShowEarViera => "Show Ears (Viera)",
|
||||
EqpEntry._54 => "Unknown 54",
|
||||
EqpEntry._55 => "Unknown 55",
|
||||
EqpEntry.HeadShowHrothgarHat => "Show on Hrothgar",
|
||||
EqpEntry.HeadShowVieraHat => "Show on Viera",
|
||||
EqpEntry.HeadUsesEvpTable => "Uses EVP Table",
|
||||
|
||||
EqpEntry._59 => "Unknown 59",
|
||||
EqpEntry._60 => "Unknown 60",
|
||||
EqpEntry._61 => "Unknown 61",
|
||||
EqpEntry._62 => "Unknown 62",
|
||||
EqpEntry._63 => "Unknown 63",
|
||||
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
|
||||
private static EqpEntry[] GetEntriesForSlot(EquipSlot slot)
|
||||
{
|
||||
return ((EqpEntry[])Enum.GetValues(typeof(EqpEntry)))
|
||||
.Where(e => e.ToEquipSlot() == slot)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static readonly EqpEntry[] EqpAttributesBody = GetEntriesForSlot(EquipSlot.Body);
|
||||
public static readonly EqpEntry[] EqpAttributesLegs = GetEntriesForSlot(EquipSlot.Legs);
|
||||
public static readonly EqpEntry[] EqpAttributesHands = GetEntriesForSlot(EquipSlot.Hands);
|
||||
public static readonly EqpEntry[] EqpAttributesFeet = GetEntriesForSlot(EquipSlot.Feet);
|
||||
public static readonly EqpEntry[] EqpAttributesHead = GetEntriesForSlot(EquipSlot.Head);
|
||||
|
||||
public static readonly IReadOnlyDictionary<EquipSlot, EqpEntry[]> EqpAttributes = new Dictionary<EquipSlot, EqpEntry[]>()
|
||||
{
|
||||
[EquipSlot.Body] = EqpAttributesBody,
|
||||
[EquipSlot.Legs] = EqpAttributesLegs,
|
||||
[EquipSlot.Hands] = EqpAttributesHands,
|
||||
[EquipSlot.Feet] = EqpAttributesFeet,
|
||||
[EquipSlot.Head] = EqpAttributesHead,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Utility;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using PseudoEquipItem = System.ValueTuple<string, ulong, ushort, ushort, ushort, byte, byte>;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly struct EquipItem
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly ulong Id;
|
||||
public readonly ushort IconId;
|
||||
public readonly SetId ModelId;
|
||||
public readonly WeaponType WeaponType;
|
||||
public readonly byte Variant;
|
||||
public readonly FullEquipType Type;
|
||||
|
||||
public uint ItemId
|
||||
=> (uint)Id;
|
||||
|
||||
public bool Valid
|
||||
=> Type != FullEquipType.Unknown;
|
||||
|
||||
public CharacterArmor Armor()
|
||||
=> new(ModelId, Variant, 0);
|
||||
|
||||
public CharacterArmor Armor(StainId stain)
|
||||
=> new(ModelId, Variant, stain);
|
||||
|
||||
public CharacterWeapon Weapon()
|
||||
=> new(ModelId, WeaponType, Variant, 0);
|
||||
|
||||
public CharacterWeapon Weapon(StainId stain)
|
||||
=> new(ModelId, WeaponType, Variant, stain);
|
||||
|
||||
public EquipItem()
|
||||
=> Name = string.Empty;
|
||||
|
||||
public EquipItem(string name, ulong id, ushort iconId, SetId modelId, WeaponType weaponType, byte variant, FullEquipType type)
|
||||
{
|
||||
Name = string.Intern(name);
|
||||
Id = id;
|
||||
IconId = iconId;
|
||||
ModelId = modelId;
|
||||
WeaponType = weaponType;
|
||||
Variant = variant;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public string ModelString
|
||||
=> WeaponType == 0 ? $"{ModelId.Value}-{Variant}" : $"{ModelId.Value}-{WeaponType.Value}-{Variant}";
|
||||
|
||||
public static implicit operator EquipItem(PseudoEquipItem it)
|
||||
=> new(it.Item1, it.Item2, it.Item3, it.Item4, it.Item5, it.Item6, (FullEquipType)it.Item7);
|
||||
|
||||
public static explicit operator PseudoEquipItem(EquipItem it)
|
||||
=> (it.Name, it.ItemId, it.IconId, (ushort)it.ModelId, (ushort)it.WeaponType, it.Variant, (byte)it.Type);
|
||||
|
||||
public static EquipItem FromArmor(Item item)
|
||||
{
|
||||
var type = item.ToEquipType();
|
||||
var name = item.Name.ToDalamudString().TextValue;
|
||||
var id = item.RowId;
|
||||
var icon = item.Icon;
|
||||
var model = (SetId)item.ModelMain;
|
||||
var weapon = (WeaponType)0;
|
||||
var variant = (byte)(item.ModelMain >> 16);
|
||||
return new EquipItem(name, id, icon, model, weapon, variant, type);
|
||||
}
|
||||
|
||||
public static EquipItem FromMainhand(Item item)
|
||||
{
|
||||
var type = item.ToEquipType();
|
||||
var name = item.Name.ToDalamudString().TextValue;
|
||||
var id = item.RowId;
|
||||
var icon = item.Icon;
|
||||
var model = (SetId)item.ModelMain;
|
||||
var weapon = (WeaponType)(item.ModelMain >> 16);
|
||||
var variant = (byte)(item.ModelMain >> 32);
|
||||
return new EquipItem(name, id, icon, model, weapon, variant, type);
|
||||
}
|
||||
|
||||
public static EquipItem FromOffhand(Item item)
|
||||
{
|
||||
var type = item.ToEquipType().ValidOffhand();
|
||||
var name = item.Name.ToDalamudString().TextValue + type.OffhandTypeSuffix();
|
||||
var id = item.RowId;
|
||||
var icon = item.Icon;
|
||||
var model = (SetId)item.ModelSub;
|
||||
var weapon = (WeaponType)(item.ModelSub >> 16);
|
||||
var variant = (byte)(item.ModelSub >> 32);
|
||||
return new EquipItem(name, id, icon, model, weapon, variant, type);
|
||||
}
|
||||
|
||||
public static EquipItem FromIds(uint itemId, ushort iconId, SetId modelId, WeaponType type, byte variant,
|
||||
FullEquipType equipType = FullEquipType.Unknown, string? name = null)
|
||||
{
|
||||
name ??= $"Unknown ({modelId.Value}-{(type.Value != 0 ? $"{type.Value}-" : string.Empty)}{variant})";
|
||||
var fullId = itemId == 0
|
||||
? modelId.Value | ((ulong)type.Value << 16) | ((ulong)variant << 32) | ((ulong)equipType << 40) | (1ul << 48)
|
||||
: itemId;
|
||||
return new EquipItem(name, fullId, iconId, modelId, type, variant, equipType);
|
||||
}
|
||||
|
||||
|
||||
public static EquipItem FromId(ulong id)
|
||||
{
|
||||
var setId = (SetId)id;
|
||||
var type = (WeaponType)(id >> 16);
|
||||
var variant = (byte)(id >> 32);
|
||||
var equipType = (FullEquipType)(id >> 40);
|
||||
return new EquipItem($"Unknown ({setId.Value}-{(type.Value != 0 ? $"{type.Value}-" : string.Empty)}{variant})", id, 0, setId, type,
|
||||
variant, equipType);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public struct GameObjectInfo : IComparable
|
||||
{
|
||||
public static GameObjectInfo Equipment( FileType type, ushort setId, GenderRace gr = GenderRace.Unknown
|
||||
, EquipSlot slot = EquipSlot.Unknown, byte variant = 0 )
|
||||
=> new()
|
||||
{
|
||||
FileType = type,
|
||||
ObjectType = slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment,
|
||||
PrimaryId = setId,
|
||||
GenderRace = gr,
|
||||
Variant = variant,
|
||||
EquipSlot = slot,
|
||||
};
|
||||
|
||||
public static GameObjectInfo Weapon( FileType type, ushort setId, ushort weaponId, byte variant = 0 )
|
||||
=> new()
|
||||
{
|
||||
FileType = type,
|
||||
ObjectType = ObjectType.Weapon,
|
||||
PrimaryId = setId,
|
||||
SecondaryId = weaponId,
|
||||
Variant = variant,
|
||||
};
|
||||
|
||||
public static GameObjectInfo Customization( FileType type, CustomizationType customizationType, ushort id = 0
|
||||
, GenderRace gr = GenderRace.Unknown, BodySlot bodySlot = BodySlot.Unknown, byte variant = 0 )
|
||||
=> new()
|
||||
{
|
||||
FileType = type,
|
||||
ObjectType = ObjectType.Character,
|
||||
PrimaryId = id,
|
||||
GenderRace = gr,
|
||||
BodySlot = bodySlot,
|
||||
Variant = variant,
|
||||
CustomizationType = customizationType,
|
||||
};
|
||||
|
||||
public static GameObjectInfo Monster( FileType type, ushort monsterId, ushort bodyId, byte variant = 0 )
|
||||
=> new()
|
||||
{
|
||||
FileType = type,
|
||||
ObjectType = ObjectType.Monster,
|
||||
PrimaryId = monsterId,
|
||||
SecondaryId = bodyId,
|
||||
Variant = variant,
|
||||
};
|
||||
|
||||
public static GameObjectInfo DemiHuman( FileType type, ushort demiHumanId, ushort bodyId, EquipSlot slot = EquipSlot.Unknown,
|
||||
byte variant = 0
|
||||
)
|
||||
=> new()
|
||||
{
|
||||
FileType = type,
|
||||
ObjectType = ObjectType.DemiHuman,
|
||||
PrimaryId = demiHumanId,
|
||||
SecondaryId = bodyId,
|
||||
Variant = variant,
|
||||
EquipSlot = slot,
|
||||
};
|
||||
|
||||
public static GameObjectInfo Map( FileType type, byte c1, byte c2, byte c3, byte c4, byte variant, byte suffix = 0 )
|
||||
=> new()
|
||||
{
|
||||
FileType = type,
|
||||
ObjectType = ObjectType.Map,
|
||||
MapC1 = c1,
|
||||
MapC2 = c2,
|
||||
MapC3 = c3,
|
||||
MapC4 = c4,
|
||||
MapSuffix = suffix,
|
||||
Variant = variant,
|
||||
};
|
||||
|
||||
public static GameObjectInfo Icon( FileType type, uint iconId, bool hq, bool hr, ClientLanguage lang = ClientLanguage.English )
|
||||
=> new()
|
||||
{
|
||||
FileType = type,
|
||||
ObjectType = ObjectType.Icon,
|
||||
IconId = iconId,
|
||||
IconHqHr = ( byte )( hq ? hr ? 3 : 1 : hr ? 2 : 0 ),
|
||||
Language = lang,
|
||||
};
|
||||
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public readonly ulong Identifier;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public FileType FileType;
|
||||
|
||||
[FieldOffset( 1 )]
|
||||
public ObjectType ObjectType;
|
||||
|
||||
|
||||
[FieldOffset( 2 )]
|
||||
public ushort PrimaryId; // Equipment, Weapon, Customization, Monster, DemiHuman
|
||||
|
||||
[FieldOffset( 2 )]
|
||||
public uint IconId; // Icon
|
||||
|
||||
[FieldOffset( 2 )]
|
||||
public byte MapC1; // Map
|
||||
|
||||
[FieldOffset( 3 )]
|
||||
public byte MapC2; // Map
|
||||
|
||||
[FieldOffset( 4 )]
|
||||
public ushort SecondaryId; // Weapon, Monster, Demihuman
|
||||
|
||||
[FieldOffset( 4 )]
|
||||
public byte MapC3; // Map
|
||||
|
||||
[FieldOffset( 4 )]
|
||||
private byte _genderRaceByte; // Equipment, Customization
|
||||
|
||||
public GenderRace GenderRace
|
||||
{
|
||||
get => Names.GenderRaceFromByte( _genderRaceByte );
|
||||
set => _genderRaceByte = value.ToByte();
|
||||
}
|
||||
|
||||
[FieldOffset( 5 )]
|
||||
public BodySlot BodySlot; // Customization
|
||||
|
||||
[FieldOffset( 5 )]
|
||||
public byte MapC4; // Map
|
||||
|
||||
[FieldOffset( 6 )]
|
||||
public byte Variant; // Equipment, Weapon, Customization, Map, Monster, Demihuman
|
||||
|
||||
[FieldOffset( 6 )]
|
||||
public byte IconHqHr; // Icon
|
||||
|
||||
[FieldOffset( 7 )]
|
||||
public EquipSlot EquipSlot; // Equipment, Demihuman
|
||||
|
||||
[FieldOffset( 7 )]
|
||||
public CustomizationType CustomizationType; // Customization
|
||||
|
||||
[FieldOffset( 7 )]
|
||||
public ClientLanguage Language; // Icon
|
||||
|
||||
[FieldOffset( 7 )]
|
||||
public byte MapSuffix;
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Identifier.GetHashCode();
|
||||
|
||||
public int CompareTo( object? r )
|
||||
=> Identifier.CompareTo( r );
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
public struct GmpEntry : IEquatable< GmpEntry >
|
||||
{
|
||||
public static readonly GmpEntry Default = new();
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => ( Value & 1 ) == 1;
|
||||
set
|
||||
{
|
||||
if( value )
|
||||
{
|
||||
Value |= 1ul;
|
||||
}
|
||||
else
|
||||
{
|
||||
Value &= ~1ul;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Animated
|
||||
{
|
||||
get => ( Value & 2 ) == 2;
|
||||
set
|
||||
{
|
||||
if( value )
|
||||
{
|
||||
Value |= 2ul;
|
||||
}
|
||||
else
|
||||
{
|
||||
Value &= ~2ul;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ushort RotationA
|
||||
{
|
||||
get => ( ushort )( ( Value >> 2 ) & 0x3FF );
|
||||
set => Value = ( Value & ~0xFFCul ) | ( ( value & 0x3FFul ) << 2 );
|
||||
}
|
||||
|
||||
public ushort RotationB
|
||||
{
|
||||
get => ( ushort )( ( Value >> 12 ) & 0x3FF );
|
||||
set => Value = ( Value & ~0x3FF000ul ) | ( ( value & 0x3FFul ) << 12 );
|
||||
}
|
||||
|
||||
public ushort RotationC
|
||||
{
|
||||
get => ( ushort )( ( Value >> 22 ) & 0x3FF );
|
||||
set => Value = ( Value & ~0xFFC00000ul ) | ( ( value & 0x3FFul ) << 22 );
|
||||
}
|
||||
|
||||
public byte UnknownA
|
||||
{
|
||||
get => ( byte )( ( Value >> 32 ) & 0x0F );
|
||||
set => Value = ( Value & ~0x0F00000000ul ) | ( ( value & 0x0Ful ) << 32 );
|
||||
}
|
||||
|
||||
public byte UnknownB
|
||||
{
|
||||
get => ( byte )( ( Value >> 36 ) & 0x0F );
|
||||
set => Value = ( Value & ~0xF000000000ul ) | ( ( value & 0x0Ful ) << 36 );
|
||||
}
|
||||
|
||||
public byte UnknownTotal
|
||||
{
|
||||
get => ( byte )( ( Value >> 32 ) & 0xFF );
|
||||
set => Value = ( Value & ~0xFF00000000ul ) | ( ( value & 0xFFul ) << 32 );
|
||||
}
|
||||
|
||||
public ulong Value { get; set; }
|
||||
|
||||
public static GmpEntry FromTexToolsMeta( byte[] data )
|
||||
{
|
||||
GmpEntry ret = new();
|
||||
using var reader = new BinaryReader( new MemoryStream( data ) );
|
||||
ret.Value = reader.ReadUInt32();
|
||||
ret.UnknownTotal = data[ 4 ];
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static implicit operator ulong( GmpEntry entry )
|
||||
=> entry.Value;
|
||||
|
||||
public static explicit operator GmpEntry( ulong entry )
|
||||
=> new() { Value = entry };
|
||||
|
||||
public bool Equals( GmpEntry other )
|
||||
=> Value == other.Value;
|
||||
|
||||
public override bool Equals( object? obj )
|
||||
=> obj is GmpEntry other && Equals( other );
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Value.GetHashCode();
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
public readonly struct ImcEntry : IEquatable<ImcEntry>
|
||||
{
|
||||
public byte MaterialId { get; init; }
|
||||
public byte DecalId { get; init; }
|
||||
public readonly ushort AttributeAndSound;
|
||||
public byte VfxId { get; init; }
|
||||
public byte MaterialAnimationId { get; init; }
|
||||
|
||||
public ushort AttributeMask
|
||||
{
|
||||
get => (ushort)(AttributeAndSound & 0x3FF);
|
||||
init => AttributeAndSound = (ushort)((AttributeAndSound & ~0x3FF) | (value & 0x3FF));
|
||||
}
|
||||
|
||||
public byte SoundId
|
||||
{
|
||||
get => (byte)(AttributeAndSound >> 10);
|
||||
init => AttributeAndSound = (ushort)(AttributeMask | (value << 10));
|
||||
}
|
||||
|
||||
public bool Equals(ImcEntry other)
|
||||
=> MaterialId == other.MaterialId
|
||||
&& DecalId == other.DecalId
|
||||
&& AttributeAndSound == other.AttributeAndSound
|
||||
&& VfxId == other.VfxId
|
||||
&& MaterialAnimationId == other.MaterialAnimationId;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is ImcEntry other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(MaterialId, DecalId, AttributeAndSound, VfxId, MaterialAnimationId);
|
||||
|
||||
[JsonConstructor]
|
||||
public ImcEntry(byte materialId, byte decalId, ushort attributeMask, byte soundId, byte vfxId, byte materialAnimationId)
|
||||
{
|
||||
MaterialId = materialId;
|
||||
DecalId = decalId;
|
||||
AttributeAndSound = 0;
|
||||
VfxId = vfxId;
|
||||
MaterialAnimationId = materialAnimationId;
|
||||
AttributeMask = attributeMask;
|
||||
SoundId = soundId;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
|
||||
public readonly struct RspEntry
|
||||
{
|
||||
public const int ByteSize = ( int )RspAttribute.NumAttributes * 4;
|
||||
|
||||
private readonly float[] Attributes;
|
||||
|
||||
public RspEntry( RspEntry copy )
|
||||
=> Attributes = ( float[] )copy.Attributes.Clone();
|
||||
|
||||
public RspEntry( byte[] bytes, int offset )
|
||||
{
|
||||
if( offset < 0 || offset + ByteSize > bytes.Length )
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
Attributes = new float[( int )RspAttribute.NumAttributes];
|
||||
using MemoryStream s = new(bytes) { Position = offset };
|
||||
using BinaryReader br = new(s);
|
||||
for( var i = 0; i < ( int )RspAttribute.NumAttributes; ++i )
|
||||
{
|
||||
Attributes[ i ] = br.ReadSingle();
|
||||
}
|
||||
}
|
||||
|
||||
private static int ToIndex( RspAttribute attribute )
|
||||
=> attribute < RspAttribute.NumAttributes && attribute >= 0
|
||||
? ( int )attribute
|
||||
: throw new InvalidEnumArgumentException();
|
||||
|
||||
public float this[ RspAttribute attribute ]
|
||||
{
|
||||
get => Attributes[ ToIndex( attribute ) ];
|
||||
set => Attributes[ ToIndex( attribute ) ] = value;
|
||||
}
|
||||
|
||||
public byte[] ToBytes()
|
||||
{
|
||||
using var s = new MemoryStream( ByteSize );
|
||||
using var bw = new BinaryWriter( s );
|
||||
foreach( var attribute in Attributes )
|
||||
{
|
||||
bw.Write( attribute );
|
||||
}
|
||||
|
||||
return s.ToArray();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
public readonly struct SetId : IComparable< SetId >, IEquatable<SetId>, IEquatable<ushort>
|
||||
{
|
||||
public readonly ushort Value;
|
||||
|
||||
public SetId( ushort value )
|
||||
=> Value = value;
|
||||
|
||||
public static implicit operator SetId( ushort id )
|
||||
=> new(id);
|
||||
|
||||
public static explicit operator ushort( SetId id )
|
||||
=> id.Value;
|
||||
|
||||
public bool Equals(SetId other)
|
||||
=> Value == other.Value;
|
||||
|
||||
public bool Equals(ushort other)
|
||||
=> Value == other;
|
||||
|
||||
public override string ToString()
|
||||
=> Value.ToString();
|
||||
|
||||
public int CompareTo( SetId other )
|
||||
=> Value.CompareTo( other.Value );
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
using Dalamud.Utility;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
// A wrapper for the clothing dyes the game provides with their RGBA color value, game ID, unmodified color value and name.
|
||||
public readonly struct Stain
|
||||
{
|
||||
// An empty stain with transparent color.
|
||||
public static readonly Stain None = new("None");
|
||||
|
||||
public readonly string Name;
|
||||
public readonly uint RgbaColor;
|
||||
public readonly byte RowIndex;
|
||||
public readonly bool Gloss;
|
||||
|
||||
public byte R
|
||||
=> (byte)(RgbaColor & 0xFF);
|
||||
|
||||
public byte G
|
||||
=> (byte)((RgbaColor >> 8) & 0xFF);
|
||||
|
||||
public byte B
|
||||
=> (byte)((RgbaColor >> 16) & 0xFF);
|
||||
|
||||
public byte Intensity
|
||||
=> (byte)((1 + R + G + B) / 3);
|
||||
|
||||
// R and B need to be shuffled and Alpha set to max.
|
||||
private static uint SeColorToRgba(uint color)
|
||||
=> ((color & 0xFF) << 16) | ((color >> 16) & 0xFF) | (color & 0xFF00) | 0xFF000000;
|
||||
|
||||
public Stain(Lumina.Excel.GeneratedSheets.Stain stain)
|
||||
: this(stain.Name.ToDalamudString().ToString(), SeColorToRgba(stain.Color), (byte)stain.RowId, stain.Unknown5)
|
||||
{ }
|
||||
|
||||
internal Stain(string name, uint dye, byte index, bool gloss)
|
||||
{
|
||||
Name = name;
|
||||
RowIndex = index;
|
||||
Gloss = gloss;
|
||||
RgbaColor = dye;
|
||||
}
|
||||
|
||||
// Only used by None.
|
||||
private Stain(string name)
|
||||
{
|
||||
Name = name;
|
||||
RowIndex = 0;
|
||||
RgbaColor = 0;
|
||||
Gloss = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
public readonly struct StainId : IEquatable< StainId >, IEqualityOperators<StainId, StainId, bool>
|
||||
{
|
||||
public readonly byte Value;
|
||||
|
||||
public StainId( byte value )
|
||||
=> Value = value;
|
||||
|
||||
public static implicit operator StainId( byte id )
|
||||
=> new(id);
|
||||
|
||||
public static explicit operator byte( StainId id )
|
||||
=> id.Value;
|
||||
|
||||
public override string ToString()
|
||||
=> Value.ToString();
|
||||
|
||||
public bool Equals( StainId other )
|
||||
=> Value == other.Value;
|
||||
|
||||
public override bool Equals( object? obj )
|
||||
=> obj is StainId other && Equals( other );
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Value.GetHashCode();
|
||||
|
||||
public static bool operator ==(StainId left, StainId right)
|
||||
=> left.Value == right.Value;
|
||||
|
||||
public static bool operator !=(StainId left, StainId right)
|
||||
=> left.Value != right.Value;
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
public readonly struct WeaponType : IEquatable< WeaponType >
|
||||
{
|
||||
public readonly ushort Value;
|
||||
|
||||
public WeaponType( ushort value )
|
||||
=> Value = value;
|
||||
|
||||
public static implicit operator WeaponType( ushort id )
|
||||
=> new(id);
|
||||
|
||||
public static explicit operator ushort( WeaponType id )
|
||||
=> id.Value;
|
||||
|
||||
public override string ToString()
|
||||
=> Value.ToString();
|
||||
|
||||
public bool Equals( WeaponType other )
|
||||
=> Value == other.Value;
|
||||
|
||||
public override bool Equals( object? obj )
|
||||
=> obj is WeaponType other && Equals( other );
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Value.GetHashCode();
|
||||
|
||||
public static bool operator ==( WeaponType lhs, WeaponType rhs )
|
||||
=> lhs.Value == rhs.Value;
|
||||
|
||||
public static bool operator !=( WeaponType lhs, WeaponType rhs )
|
||||
=> lhs.Value != rhs.Value;
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Penumbra.GameData;
|
||||
|
||||
public static class UtilityFunctions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static T? FirstOrNull<T>(this IEnumerable<T> values, Func<T, bool> predicate) where T : struct
|
||||
=> values.Cast<T?>().FirstOrDefault(v => predicate(v!.Value));
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static T[] AddItem<T>(this T[] array, T element, int count = 1)
|
||||
{
|
||||
var length = array.Length;
|
||||
var newArray = new T[array.Length + count];
|
||||
Array.Copy(array, newArray, length);
|
||||
for (var i = length; i < newArray.Length; ++i)
|
||||
newArray[i] = element;
|
||||
|
||||
return newArray;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static T[] RemoveItems<T>(this T[] array, int offset, int count = 1)
|
||||
{
|
||||
var newArray = new T[array.Length - count];
|
||||
Array.Copy(array, newArray, offset);
|
||||
Array.Copy(array, offset + count, newArray, offset, newArray.Length - offset);
|
||||
return newArray;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue