mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Glamourer-related changes.
This commit is contained in:
parent
a7ace8a8c8
commit
9e0c38169f
15 changed files with 184 additions and 90 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit e47c15c0496d1afad41cc7b2854dfc43d9b82eee
|
||||
Subproject commit e43a0f00661f319be27f606b1aa841d3234dfb0f
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
|
@ -17,10 +17,10 @@ public readonly struct ActorIdentifier : IEquatable<ActorIdentifier>
|
|||
|
||||
public enum RetainerType : ushort
|
||||
{
|
||||
Both = 0,
|
||||
Bell = 1,
|
||||
Both = 0,
|
||||
Bell = 1,
|
||||
Mannequin = 2,
|
||||
}
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
[FieldOffset( 0 )] public readonly IdentifierType Type; // All
|
||||
|
|
@ -75,7 +75,7 @@ public readonly struct ActorIdentifier : IEquatable<ActorIdentifier>
|
|||
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)
|
||||
|
|
@ -95,6 +95,9 @@ public readonly struct ActorIdentifier : IEquatable<ActorIdentifier>
|
|||
_ => "Invalid",
|
||||
};
|
||||
|
||||
public string ToName()
|
||||
=> Manager?.ToName(this) ?? "Unknown Object";
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Type switch
|
||||
{
|
||||
|
|
@ -217,6 +220,6 @@ public static class ActorManagerExtensions
|
|||
ScreenActor.FittingRoom => "Fitting Room Actor",
|
||||
ScreenActor.DyePreview => "Dye Preview Actor",
|
||||
ScreenActor.Portrait => "Portrait Actor",
|
||||
_ => "Invalid",
|
||||
_ => "Invalid",
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,23 @@ public partial class ActorManager
|
|||
};
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
|
|
@ -382,7 +399,7 @@ public partial class ActorManager
|
|||
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);
|
||||
check, withoutIndex);
|
||||
|
||||
public unsafe ActorIdentifier FromObject(GameObject? actor, bool allowPlayerNpc, bool check, bool withoutIndex)
|
||||
=> FromObject(actor, out _, allowPlayerNpc, check, withoutIndex);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Dalamud.Plugin;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using PseudoEquipItem = System.ValueTuple<string, uint, ushort, ushort, ushort, byte, byte>;
|
||||
using PseudoEquipItem = System.ValueTuple<string, ulong, ushort, ushort, ushort, byte, byte>;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
|
|
@ -51,6 +51,14 @@ internal sealed class EquipmentIdentificationList : KeyList<PseudoEquipItem>
|
|||
private static IEnumerable<PseudoEquipItem> CreateEquipmentList(DataManager gameData, ClientLanguage language)
|
||||
{
|
||||
var items = gameData.GetExcelSheet<Item>(language)!;
|
||||
return items.Where(i => ((EquipSlot)i.EquipSlotCategory.Row).IsEquipmentPiece()).Select(i => (PseudoEquipItem)EquipItem.FromArmor(i));
|
||||
return items.Where(i => ((EquipSlot)i.EquipSlotCategory.Row).IsEquipmentPiece())
|
||||
.Select(i => (PseudoEquipItem)EquipItem.FromArmor(i))
|
||||
.Concat(CustomList);
|
||||
}
|
||||
|
||||
private static IEnumerable<PseudoEquipItem> CustomList
|
||||
=> new[]
|
||||
{
|
||||
(PseudoEquipItem)EquipItem.FromIds(0, 0, 8100, 0, 1, FullEquipType.Body, "Reaper Shroud"),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Penumbra.GameData.Data;
|
|||
|
||||
public sealed class HumanModelList : DataSharer
|
||||
{
|
||||
public const string Tag = "HumanModels";
|
||||
public const string Tag = "HumanModels";
|
||||
public const int CurrentVersion = 1;
|
||||
|
||||
private readonly BitArray _humanModels;
|
||||
|
|
@ -24,6 +24,9 @@ public sealed class HumanModelList : DataSharer
|
|||
public bool IsHuman(uint modelId)
|
||||
=> modelId < _humanModels.Count && _humanModels[(int)modelId];
|
||||
|
||||
public int Count
|
||||
=> _humanModels.Count;
|
||||
|
||||
protected override void DisposeInternal()
|
||||
{
|
||||
DisposeTag(Tag);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ using Dalamud.Plugin;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using PseudoEquipItem = System.ValueTuple<string, uint, ushort, ushort, ushort, byte, byte>;
|
||||
using PseudoEquipItem = System.ValueTuple<string, ulong, ushort, ushort, ushort, byte, byte>;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ public sealed class ItemData : DataSharer, IReadOnlyDictionary<FullEquipType, IR
|
|||
{
|
||||
var list = items[(int)type];
|
||||
foreach (var item in list)
|
||||
dict.TryAdd(item.Item2, item);
|
||||
dict.TryAdd((uint) item.Item2, item);
|
||||
}
|
||||
|
||||
dict.TrimExcess();
|
||||
|
|
@ -68,7 +68,7 @@ public sealed class ItemData : DataSharer, IReadOnlyDictionary<FullEquipType, IR
|
|||
{
|
||||
var list = items[(int)type];
|
||||
foreach (var item in list)
|
||||
dict.TryAdd(item.Item2, item);
|
||||
dict.TryAdd((uint) item.Item2, item);
|
||||
}
|
||||
|
||||
dict.TrimExcess();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using Lumina.Excel.GeneratedSheets;
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
|
|
@ -23,11 +22,11 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
{
|
||||
public const int IdentificationVersion = 1;
|
||||
|
||||
public IGamePathParser GamePathParser { get; } = new GamePathParser();
|
||||
public readonly IReadOnlyList<IReadOnlyList<uint>> BnpcNames;
|
||||
public readonly IReadOnlyList<IReadOnlyList<(string Name, ObjectKind Kind)>> ModelCharaToObjects;
|
||||
public readonly IReadOnlyDictionary<string, IReadOnlyList<Action>> Actions;
|
||||
private readonly ActorManager.ActorManagerData _actorData;
|
||||
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;
|
||||
|
|
@ -49,10 +48,8 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
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);
|
||||
|
|
@ -73,6 +70,15 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
_ => _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();
|
||||
|
|
@ -125,14 +131,14 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
{
|
||||
var items = _equipment.Between(info.PrimaryId, info.EquipSlot, info.Variant);
|
||||
foreach (var item in items)
|
||||
set[item.Name.ToString()] = item;
|
||||
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.ToString()] = item;
|
||||
set[item.Name] = item;
|
||||
}
|
||||
|
||||
private void FindModel(IDictionary<string, object?> set, GameObjectInfo info)
|
||||
|
|
@ -142,10 +148,10 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
return;
|
||||
|
||||
var models = _modelIdentifierToModelChara.Between(type, info.PrimaryId, (byte)info.SecondaryId, info.Variant);
|
||||
foreach (var model in models.Where(m => m.RowId < ModelCharaToObjects.Count))
|
||||
foreach (var model in models.Where(m => m.RowId != 0 && m.RowId < ModelCharaToObjects.Count))
|
||||
{
|
||||
var objectList = ModelCharaToObjects[(int)model.RowId];
|
||||
foreach (var (name, kind) in objectList)
|
||||
foreach (var (name, kind, _) in objectList)
|
||||
set[$"{name} ({kind.ToName()})"] = model;
|
||||
}
|
||||
}
|
||||
|
|
@ -172,10 +178,10 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
case FileType.Shader:
|
||||
AddCounterString(set, FileType.Shader.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (info.ObjectType)
|
||||
{
|
||||
{
|
||||
case ObjectType.LoadingScreen:
|
||||
case ObjectType.Map:
|
||||
case ObjectType.Interface:
|
||||
|
|
@ -246,46 +252,46 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
return true;
|
||||
}
|
||||
|
||||
private IReadOnlyList<IReadOnlyList<(string Name, ObjectKind Kind)>> CreateModelObjects(ActorManager.ActorManagerData actors,
|
||||
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)>>((int)modelSheet.RowCount);
|
||||
|
||||
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)>());
|
||||
ret.Add(new ConcurrentBag<(string Name, ObjectKind Kind, uint Id)>());
|
||||
|
||||
void AddChara(int modelChara, ObjectKind kind, uint dataId)
|
||||
void AddChara(int modelChara, ObjectKind kind, uint dataId, uint displayId)
|
||||
{
|
||||
if (modelChara == 0 || modelChara >= ret.Count)
|
||||
if (modelChara >= ret.Count)
|
||||
return;
|
||||
|
||||
if (actors.TryGetName(kind, dataId, out var name))
|
||||
ret[modelChara].Add((name, kind));
|
||||
ret[modelChara].Add((name, kind, displayId));
|
||||
}
|
||||
|
||||
var oTask = Task.Run(() =>
|
||||
{
|
||||
foreach (var ornament in gameData.GetExcelSheet<Ornament>(language)!)
|
||||
AddChara(ornament.Model, (ObjectKind)15, ornament.RowId);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
AddChara((int)eNpc.ModelChara.Row, ObjectKind.EventNpc, eNpc.RowId, eNpc.RowId);
|
||||
});
|
||||
|
||||
var options = new ParallelOptions()
|
||||
|
|
@ -296,14 +302,14 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier
|
|||
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);
|
||||
AddChara((int)bNpc.ModelChara.Row, ObjectKind.BattleNpc, name, bNpc.RowId);
|
||||
});
|
||||
|
||||
Task.WaitAll(oTask, mTask, cTask, eTask);
|
||||
|
||||
return ret.Select(s => s.Count > 0
|
||||
return ret.Select(s => !s.IsEmpty
|
||||
? s.ToArray()
|
||||
: Array.Empty<(string Name, ObjectKind Kind)>()).ToArray();
|
||||
: Array.Empty<(string Name, ObjectKind Kind, uint Id)>()).ToArray();
|
||||
}
|
||||
|
||||
public static unsafe ulong KeyFromCharacterBase(CharacterBase* drawObject)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Dalamud.Plugin;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using PseudoEquipItem = System.ValueTuple<string, uint, ushort, ushort, ushort, byte, byte>;
|
||||
using PseudoEquipItem = System.ValueTuple<string, ulong, ushort, ushort, ushort, byte, byte>;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
|
|
|
|||
|
|
@ -204,6 +204,14 @@ public static class EquipSlotExtensions
|
|||
public static readonly EquipSlot[] EquipmentSlots = Enum.GetValues<EquipSlot>().Where(e => e.IsEquipment()).ToArray();
|
||||
public static readonly EquipSlot[] AccessorySlots = Enum.GetValues<EquipSlot>().Where(e => e.IsAccessory()).ToArray();
|
||||
public static readonly EquipSlot[] EqdpSlots = EquipmentSlots.Concat(AccessorySlots).ToArray();
|
||||
|
||||
public static readonly EquipSlot[] WeaponSlots =
|
||||
{
|
||||
EquipSlot.MainHand,
|
||||
EquipSlot.OffHand,
|
||||
};
|
||||
|
||||
public static readonly EquipSlot[] FullSlots = WeaponSlots.Concat(EqdpSlots).ToArray();
|
||||
}
|
||||
|
||||
public static partial class Names
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Plugin;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
|
@ -63,6 +63,14 @@ public interface IObjectIdentifier : IDisposable
|
|||
/// <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
|
||||
|
|
|
|||
|
|
@ -1,76 +1,94 @@
|
|||
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 >
|
||||
public unsafe struct CustomizeData : IEquatable<CustomizeData>, IReadOnlyCollection<byte>
|
||||
{
|
||||
public const int Size = 26;
|
||||
|
||||
public fixed byte Data[Size];
|
||||
|
||||
public void Read( void* source )
|
||||
public int Count
|
||||
=> Size;
|
||||
|
||||
public IEnumerator<byte> GetEnumerator()
|
||||
{
|
||||
fixed( byte* ptr = Data )
|
||||
for (var i = 0; i < Size; ++i)
|
||||
yield return At(i);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
|
||||
private unsafe byte At(int index)
|
||||
=> Data[index];
|
||||
|
||||
public void Read(void* source)
|
||||
{
|
||||
fixed (byte* ptr = Data)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( ptr, source, Size );
|
||||
MemoryUtility.MemCpyUnchecked(ptr, source, Size);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly void Write( void* target )
|
||||
public readonly void Write(void* target)
|
||||
{
|
||||
fixed( byte* ptr = Data )
|
||||
fixed (byte* ptr = Data)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( target, ptr, Size );
|
||||
MemoryUtility.MemCpyUnchecked(target, ptr, Size);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly CustomizeData Clone()
|
||||
{
|
||||
var ret = new CustomizeData();
|
||||
Write( ret.Data );
|
||||
Write(ret.Data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public readonly bool Equals( CustomizeData other )
|
||||
public readonly bool Equals(CustomizeData other)
|
||||
{
|
||||
fixed( byte* ptr = Data )
|
||||
fixed (byte* ptr = Data)
|
||||
{
|
||||
return MemoryUtility.MemCmpUnchecked( ptr, other.Data, Size ) == 0;
|
||||
return MemoryUtility.MemCmpUnchecked(ptr, other.Data, Size) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals( object? obj )
|
||||
=> obj is CustomizeData other && Equals( other );
|
||||
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;
|
||||
|
||||
=> 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>>
|
||||
/// 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;
|
||||
=> 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 )
|
||||
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 );
|
||||
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 )
|
||||
fixed (byte* ptr = Data)
|
||||
{
|
||||
var data = new ReadOnlySpan< byte >( ptr, Size );
|
||||
return Convert.ToBase64String( data );
|
||||
var data = new ReadOnlySpan<byte>(ptr, Size);
|
||||
return Convert.ToBase64String(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,23 +96,20 @@ public unsafe struct CustomizeData : IEquatable< CustomizeData >
|
|||
{
|
||||
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 )
|
||||
|
||||
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 )
|
||||
{
|
||||
var span = new Span<byte>(buffer, Size);
|
||||
if (!Convert.TryFromBase64String(base64, span, out var written) || written != Size)
|
||||
return false;
|
||||
}
|
||||
|
||||
Read( buffer );
|
||||
Read(buffer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
using Dalamud.Utility;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using PseudoEquipItem = System.ValueTuple<string, uint, ushort, ushort, ushort, byte, byte>;
|
||||
using PseudoEquipItem = System.ValueTuple<string, ulong, ushort, ushort, ushort, byte, byte>;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -10,13 +10,16 @@ namespace Penumbra.GameData.Structs;
|
|||
public readonly struct EquipItem
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly uint Id;
|
||||
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;
|
||||
|
||||
|
|
@ -35,7 +38,7 @@ public readonly struct EquipItem
|
|||
public EquipItem()
|
||||
=> Name = string.Empty;
|
||||
|
||||
public EquipItem(string name, uint id, ushort iconId, SetId modelId, WeaponType weaponType, byte variant, FullEquipType type)
|
||||
public EquipItem(string name, ulong id, ushort iconId, SetId modelId, WeaponType weaponType, byte variant, FullEquipType type)
|
||||
{
|
||||
Name = string.Intern(name);
|
||||
Id = id;
|
||||
|
|
@ -53,7 +56,7 @@ public readonly struct EquipItem
|
|||
=> 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.Id, it.IconId, (ushort)it.ModelId, (ushort)it.WeaponType, it.Variant, (byte)it.Type);
|
||||
=> (it.Name, it.ItemId, it.IconId, (ushort)it.ModelId, (ushort)it.WeaponType, it.Variant, (byte)it.Type);
|
||||
|
||||
public static EquipItem FromArmor(Item item)
|
||||
{
|
||||
|
|
@ -90,4 +93,28 @@ public readonly struct EquipItem
|
|||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ public sealed partial class IndividualCollections
|
|||
|
||||
identifiers = new[]
|
||||
{
|
||||
manager.CreateRetainer(retainerName, 0),
|
||||
manager.CreateRetainer(retainerName, ActorIdentifier.RetainerType.Both),
|
||||
};
|
||||
break;
|
||||
case IdentifierType.Owned:
|
||||
|
|
@ -131,7 +131,7 @@ public sealed partial class IndividualCollections
|
|||
ObjectKind.EventNpc => manager.Data.ENpcs,
|
||||
ObjectKind.Companion => manager.Data.Companions,
|
||||
ObjectKind.MountType => manager.Data.Mounts,
|
||||
(ObjectKind)15 => manager.Data.Ornaments,
|
||||
ObjectKind.Ornament => manager.Data.Ornaments,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
return table.Where(kvp => kvp.Value == name)
|
||||
|
|
|
|||
|
|
@ -122,14 +122,14 @@ public unsafe class MetaState : IDisposable
|
|||
_gameEventManager.CharacterBaseCreated -= OnCharacterBaseCreated;
|
||||
}
|
||||
|
||||
private void OnCreatingCharacterBase(uint modelCharaId, nint customize, nint equipData)
|
||||
private void OnCreatingCharacterBase(nint modelCharaId, nint customize, nint equipData)
|
||||
{
|
||||
_lastCreatedCollection = _collectionResolver.IdentifyLastGameObjectCollection(true);
|
||||
if (_lastCreatedCollection.Valid && _lastCreatedCollection.AssociatedGameObject != nint.Zero)
|
||||
_communicator.CreatingCharacterBase.Invoke(_lastCreatedCollection.AssociatedGameObject,
|
||||
_lastCreatedCollection.ModCollection.Name, (nint)(&modelCharaId), customize, equipData);
|
||||
_lastCreatedCollection.ModCollection.Name, modelCharaId, customize, equipData);
|
||||
|
||||
var decal = new DecalReverter(_config, _characterUtility, _resources, _lastCreatedCollection, UsesDecal(modelCharaId, customize));
|
||||
var decal = new DecalReverter(_config, _characterUtility, _resources, _lastCreatedCollection, UsesDecal(*(uint*)modelCharaId, customize));
|
||||
var cmp = _lastCreatedCollection.ModCollection.TemporarilySetCmpFile(_characterUtility);
|
||||
_characterBaseCreateMetaChanges.Dispose(); // Should always be empty.
|
||||
_characterBaseCreateMetaChanges = new DisposableContainer(decal, cmp);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using Dalamud.Hooking;
|
|||
using Dalamud.Utility.Signatures;
|
||||
using Penumbra.GameData;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
|
@ -154,7 +153,7 @@ public unsafe class GameEventManager : IDisposable
|
|||
{
|
||||
try
|
||||
{
|
||||
((CreatingCharacterBaseEvent)subscriber).Invoke(a, b, c);
|
||||
((CreatingCharacterBaseEvent)subscriber).Invoke((nint) (&a), b, c);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -181,7 +180,7 @@ public unsafe class GameEventManager : IDisposable
|
|||
return ret;
|
||||
}
|
||||
|
||||
public delegate void CreatingCharacterBaseEvent(uint modelCharaId, nint customize, nint equipment);
|
||||
public delegate void CreatingCharacterBaseEvent(nint modelCharaId, nint customize, nint equipment);
|
||||
public delegate void CharacterBaseCreatedEvent(uint modelCharaId, nint customize, nint equipment, nint drawObject);
|
||||
|
||||
#endregion
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue