mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Add support for imc-toggle attributes to accessories, and fix up attributes when item swapping models.
This commit is contained in:
parent
c0aa2e36ea
commit
a953febfba
2 changed files with 112 additions and 1 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Collections.Frozen;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.Collections.Cache;
|
using Penumbra.Collections.Cache;
|
||||||
|
|
@ -5,6 +6,8 @@ using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Interop;
|
using Penumbra.GameData.Interop;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Interop.Hooks.PostProcessing;
|
using Penumbra.Interop.Hooks.PostProcessing;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.Meta.Files;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
namespace Penumbra.Meta;
|
namespace Penumbra.Meta;
|
||||||
|
|
@ -58,11 +61,72 @@ public unsafe class ShapeAttributeManager : IRequiredService, IDisposable
|
||||||
_ids[(int)_modelIndex] = model.GetModelId(_modelIndex);
|
_ids[(int)_modelIndex] = model.GetModelId(_modelIndex);
|
||||||
CheckShapes(collection.MetaCache!.Shp);
|
CheckShapes(collection.MetaCache!.Shp);
|
||||||
CheckAttributes(collection.MetaCache!.Atr);
|
CheckAttributes(collection.MetaCache!.Atr);
|
||||||
|
if (_modelIndex is <= HumanSlot.LFinger and >= HumanSlot.Ears)
|
||||||
|
AccessoryImcCheck(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateDefaultMasks(model, collection.MetaCache!.Shp);
|
UpdateDefaultMasks(model, collection.MetaCache!.Shp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AccessoryImcCheck(Model model)
|
||||||
|
{
|
||||||
|
var imcMask = (ushort)(0x03FF & *(ushort*)(model.Address + 0xAAC + 6 * (int)_modelIndex));
|
||||||
|
|
||||||
|
Span<byte> attr =
|
||||||
|
[
|
||||||
|
(byte)'a',
|
||||||
|
(byte)'t',
|
||||||
|
(byte)'r',
|
||||||
|
(byte)'_',
|
||||||
|
AccessoryByte(_modelIndex),
|
||||||
|
(byte)'v',
|
||||||
|
(byte)'_',
|
||||||
|
(byte)'a',
|
||||||
|
0,
|
||||||
|
];
|
||||||
|
for (var i = 1; i < 10; ++i)
|
||||||
|
{
|
||||||
|
var flag = (ushort)(1 << i);
|
||||||
|
if ((imcMask & flag) is not 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
attr[^2] = (byte)('a' + i);
|
||||||
|
|
||||||
|
foreach (var (attribute, index) in _model->ModelResourceHandle->Attributes)
|
||||||
|
{
|
||||||
|
if (!EqualAttribute(attr, attribute.Value))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
_model->EnabledAttributeIndexMask &= ~(1u << index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool EqualAttribute(Span<byte> needle, byte* haystack)
|
||||||
|
{
|
||||||
|
foreach (var character in needle)
|
||||||
|
{
|
||||||
|
if (*haystack++ != character)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte AccessoryByte(HumanSlot slot)
|
||||||
|
=> slot switch
|
||||||
|
{
|
||||||
|
HumanSlot.Head => (byte)'m',
|
||||||
|
HumanSlot.Ears => (byte)'e',
|
||||||
|
HumanSlot.Neck => (byte)'n',
|
||||||
|
HumanSlot.Wrists => (byte)'w',
|
||||||
|
HumanSlot.RFinger => (byte)'r',
|
||||||
|
HumanSlot.LFinger => (byte)'r',
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
private void CheckAttributes(AtrCache attributeCache)
|
private void CheckAttributes(AtrCache attributeCache)
|
||||||
{
|
{
|
||||||
if (attributeCache.DisabledCount is 0)
|
if (attributeCache.DisabledCount is 0)
|
||||||
|
|
|
||||||
|
|
@ -234,9 +234,56 @@ public static class EquipmentSwap
|
||||||
mdl.ChildSwaps.Add(mtrl);
|
mdl.ChildSwaps.Add(mtrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FixAttributes(mdl, slotFrom, slotTo);
|
||||||
|
|
||||||
return mdl;
|
return mdl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void FixAttributes(FileSwap swap, EquipSlot slotFrom, EquipSlot slotTo)
|
||||||
|
{
|
||||||
|
if (slotFrom == slotTo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var needle = slotTo switch
|
||||||
|
{
|
||||||
|
EquipSlot.Head => "atr_mv_",
|
||||||
|
EquipSlot.Ears => "atr_ev_",
|
||||||
|
EquipSlot.Neck => "atr_nv_",
|
||||||
|
EquipSlot.Wrists => "atr_wv_",
|
||||||
|
EquipSlot.RFinger or EquipSlot.LFinger => "atr_rv_",
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
var replacement = slotFrom switch
|
||||||
|
{
|
||||||
|
EquipSlot.Head => 'm',
|
||||||
|
EquipSlot.Ears => 'e',
|
||||||
|
EquipSlot.Neck => 'n',
|
||||||
|
EquipSlot.Wrists => 'w',
|
||||||
|
EquipSlot.RFinger or EquipSlot.LFinger => 'r',
|
||||||
|
_ => 'm',
|
||||||
|
};
|
||||||
|
|
||||||
|
var attributes = swap.AsMdl()!.Attributes;
|
||||||
|
for (var i = 0; i < attributes.Length; ++i)
|
||||||
|
{
|
||||||
|
if (FixAttribute(ref attributes[i], needle, replacement))
|
||||||
|
swap.DataWasChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe bool FixAttribute(ref string attribute, string from, char to)
|
||||||
|
{
|
||||||
|
if (!attribute.StartsWith(from) || attribute.Length != from.Length + 1 || attribute[^1] is < 'a' or > 'j')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Span<char> stack = stackalloc char[attribute.Length];
|
||||||
|
attribute.CopyTo(stack);
|
||||||
|
stack[4] = to;
|
||||||
|
attribute = new string(stack);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static void LookupItem(EquipItem i, out EquipSlot slot, out PrimaryId modelId, out Variant variant)
|
private static void LookupItem(EquipItem i, out EquipSlot slot, out PrimaryId modelId, out Variant variant)
|
||||||
{
|
{
|
||||||
slot = i.Type.ToSlot();
|
slot = i.Type.ToSlot();
|
||||||
|
|
@ -399,7 +446,7 @@ public static class EquipmentSwap
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var folderTo = GamePaths.Mtrl.GearFolder(slotTo, idTo, variantTo);
|
var folderTo = GamePaths.Mtrl.GearFolder(slotTo, idTo, variantTo);
|
||||||
var pathTo = $"{folderTo}{fileName}";
|
var pathTo = $"{folderTo}{fileName}";
|
||||||
|
|
||||||
var folderFrom = GamePaths.Mtrl.GearFolder(slotFrom, idFrom, variantTo);
|
var folderFrom = GamePaths.Mtrl.GearFolder(slotFrom, idFrom, variantTo);
|
||||||
var newFileName = ItemSwap.ReplaceId(fileName, prefix, idTo, idFrom);
|
var newFileName = ItemSwap.ReplaceId(fileName, prefix, idTo, idFrom);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue