mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Merge branch 'rt-dt'
This commit is contained in:
commit
a6ee4c96ea
3 changed files with 77 additions and 54 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
|
using OtterGui.Text.HelperObjects;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
@ -8,26 +9,33 @@ using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using static Penumbra.Interop.Structs.StructExtensions;
|
using static Penumbra.Interop.Structs.StructExtensions;
|
||||||
|
using CharaBase = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBase;
|
||||||
using ModelType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBase.ModelType;
|
using ModelType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBase.ModelType;
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceTree;
|
namespace Penumbra.Interop.ResourceTree;
|
||||||
|
|
||||||
internal partial record ResolveContext
|
internal partial record ResolveContext
|
||||||
{
|
{
|
||||||
|
private static bool IsEquipmentOrAccessorySlot(uint slotIndex)
|
||||||
|
=> slotIndex is < 10 or 16 or 17;
|
||||||
|
|
||||||
|
private static bool IsEquipmentSlot(uint slotIndex)
|
||||||
|
=> slotIndex is < 5 or 16 or 17;
|
||||||
|
|
||||||
private Utf8GamePath ResolveModelPath()
|
private Utf8GamePath ResolveModelPath()
|
||||||
{
|
{
|
||||||
// Correctness:
|
// Correctness:
|
||||||
// Resolving a model path through the game's code can use EQDP metadata for human equipment models.
|
// Resolving a model path through the game's code can use EQDP metadata for human equipment models.
|
||||||
return ModelType switch
|
return ModelType switch
|
||||||
{
|
{
|
||||||
ModelType.Human when SlotIndex < 10 => ResolveEquipmentModelPath(),
|
ModelType.Human when IsEquipmentOrAccessorySlot(SlotIndex) => ResolveEquipmentModelPath(),
|
||||||
_ => ResolveModelPathNative(),
|
_ => ResolveModelPathNative(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Utf8GamePath ResolveEquipmentModelPath()
|
private Utf8GamePath ResolveEquipmentModelPath()
|
||||||
{
|
{
|
||||||
var path = SlotIndex < 5
|
var path = IsEquipmentSlot(SlotIndex)
|
||||||
? GamePaths.Equipment.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), Slot)
|
? GamePaths.Equipment.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), Slot)
|
||||||
: GamePaths.Accessory.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), Slot);
|
: GamePaths.Accessory.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), Slot);
|
||||||
return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty;
|
return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty;
|
||||||
|
|
@ -39,7 +47,7 @@ internal partial record ResolveContext
|
||||||
private unsafe GenderRace ResolveEqdpRaceCode(EquipSlot slot, PrimaryId primaryId)
|
private unsafe GenderRace ResolveEqdpRaceCode(EquipSlot slot, PrimaryId primaryId)
|
||||||
{
|
{
|
||||||
var slotIndex = slot.ToIndex();
|
var slotIndex = slot.ToIndex();
|
||||||
if (slotIndex >= 10 || ModelType != ModelType.Human)
|
if (!IsEquipmentOrAccessorySlot(slotIndex) || ModelType != ModelType.Human)
|
||||||
return GenderRace.MidlanderMale;
|
return GenderRace.MidlanderMale;
|
||||||
|
|
||||||
var characterRaceCode = (GenderRace)((Human*)CharacterBase)->RaceSexId;
|
var characterRaceCode = (GenderRace)((Human*)CharacterBase)->RaceSexId;
|
||||||
|
|
@ -80,7 +88,7 @@ internal partial record ResolveContext
|
||||||
// Resolving a material path through the game's code can dereference null pointers for materials that involve IMC metadata.
|
// Resolving a material path through the game's code can dereference null pointers for materials that involve IMC metadata.
|
||||||
return ModelType switch
|
return ModelType switch
|
||||||
{
|
{
|
||||||
ModelType.Human when SlotIndex is < 10 or 16 && mtrlFileName[8] != (byte)'b'
|
ModelType.Human when IsEquipmentOrAccessorySlot(SlotIndex) && mtrlFileName[8] != (byte)'b'
|
||||||
=> ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName),
|
=> ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName),
|
||||||
ModelType.DemiHuman => ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName),
|
ModelType.DemiHuman => ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName),
|
||||||
ModelType.Weapon => ResolveWeaponMaterialPath(modelPath, imc, mtrlFileName),
|
ModelType.Weapon => ResolveWeaponMaterialPath(modelPath, imc, mtrlFileName),
|
||||||
|
|
@ -95,7 +103,7 @@ internal partial record ResolveContext
|
||||||
var variant = ResolveMaterialVariant(imc, Equipment.Variant);
|
var variant = ResolveMaterialVariant(imc, Equipment.Variant);
|
||||||
var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName);
|
var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName);
|
||||||
|
|
||||||
Span<byte> pathBuffer = stackalloc byte[260];
|
Span<byte> pathBuffer = stackalloc byte[CharaBase.PathBufferSize];
|
||||||
pathBuffer = AssembleMaterialPath(pathBuffer, modelPath.Path.Span, variant, fileName);
|
pathBuffer = AssembleMaterialPath(pathBuffer, modelPath.Path.Span, variant, fileName);
|
||||||
|
|
||||||
return Utf8GamePath.FromSpan(pathBuffer, MetaDataComputation.None, out var path) ? path.Clone() : Utf8GamePath.Empty;
|
return Utf8GamePath.FromSpan(pathBuffer, MetaDataComputation.None, out var path) ? path.Clone() : Utf8GamePath.Empty;
|
||||||
|
|
@ -109,32 +117,26 @@ internal partial record ResolveContext
|
||||||
if (setIdHigh is 20 && mtrlFileName[14] == (byte)'c')
|
if (setIdHigh is 20 && mtrlFileName[14] == (byte)'c')
|
||||||
return Utf8GamePath.FromString(GamePaths.Weapon.Mtrl.Path(2001, 1, 1, "c"), out var path) ? path : Utf8GamePath.Empty;
|
return Utf8GamePath.FromString(GamePaths.Weapon.Mtrl.Path(2001, 1, 1, "c"), out var path) ? path : Utf8GamePath.Empty;
|
||||||
|
|
||||||
// MNK (03??, 16??), NIN (18??) and DNC (26??) offhands share materials with the corresponding mainhand
|
// Some offhands share materials with the corresponding mainhand
|
||||||
if (setIdHigh is 3 or 16 or 18 or 26)
|
if (ItemData.AdaptOffhandImc(Equipment.Set.Id, out var mirroredSetId))
|
||||||
{
|
|
||||||
var setIdLow = Equipment.Set.Id % 100;
|
|
||||||
if (setIdLow > 50)
|
|
||||||
{
|
{
|
||||||
var variant = ResolveMaterialVariant(imc, Equipment.Variant);
|
var variant = ResolveMaterialVariant(imc, Equipment.Variant);
|
||||||
var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName);
|
var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName);
|
||||||
|
|
||||||
var mirroredSetId = (ushort)(Equipment.Set.Id - 50);
|
|
||||||
|
|
||||||
Span<byte> mirroredFileName = stackalloc byte[32];
|
Span<byte> mirroredFileName = stackalloc byte[32];
|
||||||
mirroredFileName = mirroredFileName[..fileName.Length];
|
mirroredFileName = mirroredFileName[..fileName.Length];
|
||||||
fileName.CopyTo(mirroredFileName);
|
fileName.CopyTo(mirroredFileName);
|
||||||
WriteZeroPaddedNumber(mirroredFileName[4..8], mirroredSetId);
|
WriteZeroPaddedNumber(mirroredFileName[4..8], mirroredSetId.Id);
|
||||||
|
|
||||||
Span<byte> pathBuffer = stackalloc byte[260];
|
Span<byte> pathBuffer = stackalloc byte[CharaBase.PathBufferSize];
|
||||||
pathBuffer = AssembleMaterialPath(pathBuffer, modelPath.Path.Span, variant, mirroredFileName);
|
pathBuffer = AssembleMaterialPath(pathBuffer, modelPath.Path.Span, variant, mirroredFileName);
|
||||||
|
|
||||||
var weaponPosition = pathBuffer.IndexOf("/weapon/w"u8);
|
var weaponPosition = pathBuffer.IndexOf("/weapon/w"u8);
|
||||||
if (weaponPosition >= 0)
|
if (weaponPosition >= 0)
|
||||||
WriteZeroPaddedNumber(pathBuffer[(weaponPosition + 9)..(weaponPosition + 13)], mirroredSetId);
|
WriteZeroPaddedNumber(pathBuffer[(weaponPosition + 9)..(weaponPosition + 13)], mirroredSetId.Id);
|
||||||
|
|
||||||
return Utf8GamePath.FromSpan(pathBuffer, MetaDataComputation.None, out var path) ? path.Clone() : Utf8GamePath.Empty;
|
return Utf8GamePath.FromSpan(pathBuffer, MetaDataComputation.None, out var path) ? path.Clone() : Utf8GamePath.Empty;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName);
|
return ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName);
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +146,7 @@ internal partial record ResolveContext
|
||||||
var variant = ResolveMaterialVariant(imc, (byte)((Monster*)CharacterBase)->Variant);
|
var variant = ResolveMaterialVariant(imc, (byte)((Monster*)CharacterBase)->Variant);
|
||||||
var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName);
|
var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName);
|
||||||
|
|
||||||
Span<byte> pathBuffer = stackalloc byte[260];
|
Span<byte> pathBuffer = stackalloc byte[CharaBase.PathBufferSize];
|
||||||
pathBuffer = AssembleMaterialPath(pathBuffer, modelPath.Path.Span, variant, fileName);
|
pathBuffer = AssembleMaterialPath(pathBuffer, modelPath.Path.Span, variant, fileName);
|
||||||
|
|
||||||
return Utf8GamePath.FromSpan(pathBuffer, MetaDataComputation.None, out var path) ? path.Clone() : Utf8GamePath.Empty;
|
return Utf8GamePath.FromSpan(pathBuffer, MetaDataComputation.None, out var path) ? path.Clone() : Utf8GamePath.Empty;
|
||||||
|
|
@ -175,13 +177,21 @@ internal partial record ResolveContext
|
||||||
|
|
||||||
var baseDirectory = modelPath[..modelPosition];
|
var baseDirectory = modelPath[..modelPosition];
|
||||||
|
|
||||||
baseDirectory.CopyTo(materialPathBuffer);
|
var writer = new SpanTextWriter(materialPathBuffer);
|
||||||
"/material/v"u8.CopyTo(materialPathBuffer[baseDirectory.Length..]);
|
writer.Append(baseDirectory);
|
||||||
WriteZeroPaddedNumber(materialPathBuffer.Slice(baseDirectory.Length + 11, 4), variant);
|
writer.Append("/material/v"u8);
|
||||||
materialPathBuffer[baseDirectory.Length + 15] = (byte)'/';
|
WriteZeroPaddedNumber(ref writer, 4, variant);
|
||||||
mtrlFileName.CopyTo(materialPathBuffer[(baseDirectory.Length + 16)..]);
|
writer.Append((byte)'/');
|
||||||
|
writer.Append(mtrlFileName);
|
||||||
|
writer.EnsureNullTerminated();
|
||||||
|
|
||||||
return materialPathBuffer[..(baseDirectory.Length + 16 + mtrlFileName.Length)];
|
return materialPathBuffer[..writer.Position];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteZeroPaddedNumber(ref SpanTextWriter writer, int width, ushort number)
|
||||||
|
{
|
||||||
|
WriteZeroPaddedNumber(writer.GetRemainingSpan()[..width], number);
|
||||||
|
writer.Advance(width);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteZeroPaddedNumber(Span<byte> destination, ushort number)
|
private static void WriteZeroPaddedNumber(Span<byte> destination, ushort number)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
using FFXIVClientStructs.Interop;
|
using FFXIVClientStructs.Interop;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
using OtterGui.Text.HelperObjects;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
|
|
@ -16,7 +16,7 @@ using Penumbra.String;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
using static Penumbra.Interop.Structs.StructExtensions;
|
using static Penumbra.Interop.Structs.StructExtensions;
|
||||||
using ModelType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBase.ModelType;
|
using CharaBase = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBase;
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceTree;
|
namespace Penumbra.Interop.ResourceTree;
|
||||||
|
|
||||||
|
|
@ -29,25 +29,25 @@ internal record GlobalResolveContext(
|
||||||
{
|
{
|
||||||
public readonly Dictionary<(Utf8GamePath, nint), ResourceNode> Nodes = new(128);
|
public readonly Dictionary<(Utf8GamePath, nint), ResourceNode> Nodes = new(128);
|
||||||
|
|
||||||
public unsafe ResolveContext CreateContext(CharacterBase* characterBase, uint slotIndex = 0xFFFFFFFFu,
|
public unsafe ResolveContext CreateContext(CharaBase* characterBase, uint slotIndex = 0xFFFFFFFFu,
|
||||||
EquipSlot slot = EquipSlot.Unknown, CharacterArmor equipment = default, SecondaryId secondaryId = default)
|
EquipSlot slot = EquipSlot.Unknown, CharacterArmor equipment = default, SecondaryId secondaryId = default)
|
||||||
=> new(this, characterBase, slotIndex, slot, equipment, secondaryId);
|
=> new(this, characterBase, slotIndex, slot, equipment, secondaryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal unsafe partial record ResolveContext(
|
internal unsafe partial record ResolveContext(
|
||||||
GlobalResolveContext Global,
|
GlobalResolveContext Global,
|
||||||
Pointer<CharacterBase> CharacterBasePointer,
|
Pointer<CharaBase> CharacterBasePointer,
|
||||||
uint SlotIndex,
|
uint SlotIndex,
|
||||||
EquipSlot Slot,
|
EquipSlot Slot,
|
||||||
CharacterArmor Equipment,
|
CharacterArmor Equipment,
|
||||||
SecondaryId SecondaryId)
|
SecondaryId SecondaryId)
|
||||||
{
|
{
|
||||||
public CharacterBase* CharacterBase
|
public CharaBase* CharacterBase
|
||||||
=> CharacterBasePointer.Value;
|
=> CharacterBasePointer.Value;
|
||||||
|
|
||||||
private static readonly CiByteString ShpkPrefix = CiByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true);
|
private static readonly CiByteString ShpkPrefix = CiByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true);
|
||||||
|
|
||||||
private ModelType ModelType
|
private CharaBase.ModelType ModelType
|
||||||
=> CharacterBase->GetModelType();
|
=> CharacterBase->GetModelType();
|
||||||
|
|
||||||
private ResourceNode? CreateNodeFromShpk(ShaderPackageResourceHandle* resourceHandle, CiByteString gamePath)
|
private ResourceNode? CreateNodeFromShpk(ShaderPackageResourceHandle* resourceHandle, CiByteString gamePath)
|
||||||
|
|
@ -75,11 +75,14 @@ internal unsafe partial record ResolveContext(
|
||||||
if (lastDirectorySeparator == -1 || lastDirectorySeparator > gamePath.Length - 3)
|
if (lastDirectorySeparator == -1 || lastDirectorySeparator > gamePath.Length - 3)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
Span<byte> prefixed = stackalloc byte[260];
|
Span<byte> prefixed = stackalloc byte[CharaBase.PathBufferSize];
|
||||||
gamePath.Span[..(lastDirectorySeparator + 1)].CopyTo(prefixed);
|
|
||||||
prefixed[lastDirectorySeparator + 1] = (byte)'-';
|
var writer = new SpanTextWriter(prefixed);
|
||||||
prefixed[lastDirectorySeparator + 2] = (byte)'-';
|
writer.Append(gamePath.Span[..(lastDirectorySeparator + 1)]);
|
||||||
gamePath.Span[(lastDirectorySeparator + 1)..].CopyTo(prefixed[(lastDirectorySeparator + 3)..]);
|
writer.Append((byte)'-');
|
||||||
|
writer.Append((byte)'-');
|
||||||
|
writer.Append(gamePath.Span[(lastDirectorySeparator + 1)..]);
|
||||||
|
writer.EnsureNullTerminated();
|
||||||
|
|
||||||
if (!Utf8GamePath.FromSpan(prefixed[..(gamePath.Length + 2)], MetaDataComputation.None, out var tmp))
|
if (!Utf8GamePath.FromSpan(prefixed[..(gamePath.Length + 2)], MetaDataComputation.None, out var tmp))
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using Penumbra.Interop.Hooks.PostProcessing;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
using CustomizeData = FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData;
|
using CustomizeData = FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData;
|
||||||
using CustomizeIndex = Dalamud.Game.ClientState.Objects.Enums.CustomizeIndex;
|
using CustomizeIndex = Dalamud.Game.ClientState.Objects.Enums.CustomizeIndex;
|
||||||
|
using ModelType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBase.ModelType;
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceTree;
|
namespace Penumbra.Interop.ResourceTree;
|
||||||
|
|
||||||
|
|
@ -44,8 +45,8 @@ public class ResourceTree
|
||||||
PlayerRelated = playerRelated;
|
PlayerRelated = playerRelated;
|
||||||
CollectionName = collectionName;
|
CollectionName = collectionName;
|
||||||
AnonymizedCollectionName = anonymizedCollectionName;
|
AnonymizedCollectionName = anonymizedCollectionName;
|
||||||
Nodes = new List<ResourceNode>();
|
Nodes = [];
|
||||||
FlatNodes = new HashSet<ResourceNode>();
|
FlatNodes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ProcessPostfix(Action<ResourceNode, ResourceNode?> action)
|
public void ProcessPostfix(Action<ResourceNode, ResourceNode?> action)
|
||||||
|
|
@ -59,13 +60,13 @@ public class ResourceTree
|
||||||
var character = (Character*)GameObjectAddress;
|
var character = (Character*)GameObjectAddress;
|
||||||
var model = (CharacterBase*)DrawObjectAddress;
|
var model = (CharacterBase*)DrawObjectAddress;
|
||||||
var modelType = model->GetModelType();
|
var modelType = model->GetModelType();
|
||||||
var human = modelType == CharacterBase.ModelType.Human ? (Human*)model : null;
|
var human = modelType == ModelType.Human ? (Human*)model : null;
|
||||||
var equipment = modelType switch
|
var equipment = modelType switch
|
||||||
{
|
{
|
||||||
CharacterBase.ModelType.Human => new ReadOnlySpan<CharacterArmor>(&human->Head, 10),
|
ModelType.Human => new ReadOnlySpan<CharacterArmor>(&human->Head, 12),
|
||||||
CharacterBase.ModelType.DemiHuman => new ReadOnlySpan<CharacterArmor>(
|
ModelType.DemiHuman => new ReadOnlySpan<CharacterArmor>(
|
||||||
Unsafe.AsPointer(ref character->DrawData.EquipmentModelIds[0]), 10),
|
Unsafe.AsPointer(ref character->DrawData.EquipmentModelIds[0]), 10),
|
||||||
_ => ReadOnlySpan<CharacterArmor>.Empty,
|
_ => [],
|
||||||
};
|
};
|
||||||
ModelId = character->CharacterData.ModelCharaId;
|
ModelId = character->CharacterData.ModelCharaId;
|
||||||
CustomizeData = character->DrawData.CustomizeData;
|
CustomizeData = character->DrawData.CustomizeData;
|
||||||
|
|
@ -75,9 +76,18 @@ public class ResourceTree
|
||||||
|
|
||||||
for (var i = 0u; i < model->SlotCount; ++i)
|
for (var i = 0u; i < model->SlotCount; ++i)
|
||||||
{
|
{
|
||||||
var slotContext = i < equipment.Length
|
var slotContext = modelType switch
|
||||||
|
{
|
||||||
|
ModelType.Human => i switch
|
||||||
|
{
|
||||||
|
< 10 => globalContext.CreateContext(model, i, i.ToEquipSlot(), equipment[(int)i]),
|
||||||
|
16 or 17 => globalContext.CreateContext(model, i, EquipSlot.Head, equipment[(int)(i - 6)]),
|
||||||
|
_ => globalContext.CreateContext(model, i),
|
||||||
|
},
|
||||||
|
_ => i < equipment.Length
|
||||||
? globalContext.CreateContext(model, i, i.ToEquipSlot(), equipment[(int)i])
|
? globalContext.CreateContext(model, i, i.ToEquipSlot(), equipment[(int)i])
|
||||||
: globalContext.CreateContext(model, i);
|
: globalContext.CreateContext(model, i),
|
||||||
|
};
|
||||||
|
|
||||||
var imc = (ResourceHandle*)model->IMCArray[i];
|
var imc = (ResourceHandle*)model->IMCArray[i];
|
||||||
var imcNode = slotContext.CreateNodeFromImc(imc);
|
var imcNode = slotContext.CreateNodeFromImc(imc);
|
||||||
|
|
@ -117,7 +127,7 @@ public class ResourceTree
|
||||||
|
|
||||||
var subObject = (CharacterBase*)baseSubObject;
|
var subObject = (CharacterBase*)baseSubObject;
|
||||||
|
|
||||||
if (subObject->GetModelType() != CharacterBase.ModelType.Weapon)
|
if (subObject->GetModelType() != ModelType.Weapon)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var weapon = (Weapon*)subObject;
|
var weapon = (Weapon*)subObject;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue