Stains and misc fixes ?

This commit is contained in:
Aspher0 2024-07-11 08:26:22 +02:00
parent fa43ec5e73
commit 8a954e6e09
22 changed files with 157 additions and 134 deletions

View file

@ -117,7 +117,7 @@ public class DesignBase64Migration
} }
data.SetItem(slot, item); data.SetItem(slot, item);
data.SetStain(slot, mdl.Stain); data.SetStain(slot, mdl.Stains);
} }
var main = cur[0].Skeleton.Id == 0 var main = cur[0].Skeleton.Id == 0
@ -130,7 +130,7 @@ public class DesignBase64Migration
} }
data.SetItem(EquipSlot.MainHand, main); data.SetItem(EquipSlot.MainHand, main);
data.SetStain(EquipSlot.MainHand, cur[0].Stain); data.SetStain(EquipSlot.MainHand, cur[0].Stains);
EquipItem off; EquipItem off;
// Fist weapon hack // Fist weapon hack
@ -141,7 +141,7 @@ public class DesignBase64Migration
if (gauntlet.Valid) if (gauntlet.Valid)
{ {
data.SetItem(EquipSlot.Hands, gauntlet); data.SetItem(EquipSlot.Hands, gauntlet);
data.SetStain(EquipSlot.Hands, cur[0].Stain); data.SetStain(EquipSlot.Hands, cur[0].Stains);
} }
} }
else else
@ -158,7 +158,7 @@ public class DesignBase64Migration
} }
data.SetItem(EquipSlot.OffHand, off); data.SetItem(EquipSlot.OffHand, off);
data.SetStain(EquipSlot.OffHand, cur[1].Stain); data.SetStain(EquipSlot.OffHand, cur[1].Stains);
return data; return data;
} }
} }

View file

@ -194,7 +194,7 @@ public class DesignConverter(
item = ItemManager.NothingItem(slot); item = ItemManager.NothingItem(slot);
} }
yield return (slot, item, armor.Stain); yield return (slot, item, armor.Stains.Stain1); // To change
} }
var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant); var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant);
@ -204,7 +204,7 @@ public class DesignConverter(
mh = _items.DefaultSword; mh = _items.DefaultSword;
} }
yield return (EquipSlot.MainHand, mh, mainhand.Stain); yield return (EquipSlot.MainHand, mh, mainhand.Stains.Stain1); // To change
var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type); var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type);
if (!skipWarnings && !oh.Valid) if (!skipWarnings && !oh.Valid)
@ -215,7 +215,7 @@ public class DesignConverter(
oh = ItemManager.NothingItem(FullEquipType.Shield); oh = ItemManager.NothingItem(FullEquipType.Shield);
} }
yield return (EquipSlot.OffHand, oh, offhand.Stain); yield return (EquipSlot.OffHand, oh, offhand.Stains.Stain1); // To change
} }
private static void ComputeMaterials(DesignMaterialManager manager, in StateMaterialManager materials, private static void ComputeMaterials(DesignMaterialManager manager, in StateMaterialManager materials,

View file

@ -125,7 +125,7 @@ public unsafe struct DesignData
return false; return false;
_itemIds[index] = item.ItemId.Id; _itemIds[index] = item.ItemId.Id;
_iconIds[index] = item.IconId.Id; _iconIds[index] = (ushort)item.IconId.Id;
_equipmentBytes[4 * index + 0] = (byte)item.PrimaryId.Id; _equipmentBytes[4 * index + 0] = (byte)item.PrimaryId.Id;
_equipmentBytes[4 * index + 1] = (byte)(item.PrimaryId.Id >> 8); _equipmentBytes[4 * index + 1] = (byte)(item.PrimaryId.Id >> 8);
_equipmentBytes[4 * index + 2] = item.Variant.Id; _equipmentBytes[4 * index + 2] = item.Variant.Id;
@ -158,21 +158,21 @@ public unsafe struct DesignData
return true; return true;
} }
public bool SetStain(EquipSlot slot, StainId stain) public bool SetStain(EquipSlot slot, StainIds stains)
=> slot.ToIndex() switch => slot.ToIndex() switch
{ {
0 => SetIfDifferent(ref _equipmentBytes[3], stain.Id), 0 => SetIfDifferent(ref _equipmentBytes[3], stains),
1 => SetIfDifferent(ref _equipmentBytes[7], stain.Id), 1 => SetIfDifferent(ref _equipmentBytes[7], stains),
2 => SetIfDifferent(ref _equipmentBytes[11], stain.Id), 2 => SetIfDifferent(ref _equipmentBytes[11], stains),
3 => SetIfDifferent(ref _equipmentBytes[15], stain.Id), 3 => SetIfDifferent(ref _equipmentBytes[15], stains),
4 => SetIfDifferent(ref _equipmentBytes[19], stain.Id), 4 => SetIfDifferent(ref _equipmentBytes[19], stains),
5 => SetIfDifferent(ref _equipmentBytes[23], stain.Id), 5 => SetIfDifferent(ref _equipmentBytes[23], stains),
6 => SetIfDifferent(ref _equipmentBytes[27], stain.Id), 6 => SetIfDifferent(ref _equipmentBytes[27], stains),
7 => SetIfDifferent(ref _equipmentBytes[31], stain.Id), 7 => SetIfDifferent(ref _equipmentBytes[31], stains),
8 => SetIfDifferent(ref _equipmentBytes[35], stain.Id), 8 => SetIfDifferent(ref _equipmentBytes[35], stains),
9 => SetIfDifferent(ref _equipmentBytes[39], stain.Id), 9 => SetIfDifferent(ref _equipmentBytes[39], stains),
10 => SetIfDifferent(ref _equipmentBytes[43], stain.Id), 10 => SetIfDifferent(ref _equipmentBytes[43], stains),
11 => SetIfDifferent(ref _equipmentBytes[47], stain.Id), 11 => SetIfDifferent(ref _equipmentBytes[47], stains),
_ => false, _ => false,
}; };
@ -260,15 +260,15 @@ public unsafe struct DesignData
foreach (var slot in EquipSlotExtensions.EqdpSlots) foreach (var slot in EquipSlotExtensions.EqdpSlots)
{ {
SetItem(slot, ItemManager.NothingItem(slot)); SetItem(slot, ItemManager.NothingItem(slot));
SetStain(slot, 0); SetStain(slot, StainIds.None);
SetCrest(slot.ToCrestFlag(), false); SetCrest(slot.ToCrestFlag(), false);
} }
SetItem(EquipSlot.MainHand, items.DefaultSword); SetItem(EquipSlot.MainHand, items.DefaultSword);
SetStain(EquipSlot.MainHand, 0); SetStain(EquipSlot.MainHand, StainIds.None);
SetCrest(CrestFlag.MainHand, false); SetCrest(CrestFlag.MainHand, false);
SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield));
SetStain(EquipSlot.OffHand, 0); SetStain(EquipSlot.OffHand, StainIds.None);
SetCrest(CrestFlag.OffHand, false); SetCrest(CrestFlag.OffHand, false);
} }

View file

@ -1,5 +1,6 @@
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures; using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Services; using OtterGui.Services;
@ -33,7 +34,7 @@ public class CustomizeManager : IAsyncDataContainer
} }
/// <summary> Get specific icons. </summary> /// <summary> Get specific icons. </summary>
public ISharedImmediateTexture GetIcon(uint id) public IDalamudTextureWrap GetIcon(uint id)
=> _icons.LoadIcon(id)!; => _icons.LoadIcon(id)!;
/// <summary> Iterate over all supported genders and clans. </summary> /// <summary> Iterate over all supported genders and clans. </summary>
@ -48,7 +49,7 @@ public class CustomizeManager : IAsyncDataContainer
public CustomizeManager(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) public CustomizeManager(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet)
{ {
_icons = new IconStorage(textures, gameData); _icons = new TextureCache(gameData, textures);
var stopwatch = new Stopwatch(); var stopwatch = new Stopwatch();
var tmpTask = Task.Run(() => var tmpTask = Task.Run(() =>
{ {
@ -73,7 +74,7 @@ public class CustomizeManager : IAsyncDataContainer
public bool Finished public bool Finished
=> Awaiter.IsCompletedSuccessfully; => Awaiter.IsCompletedSuccessfully;
private readonly IconStorage _icons; private readonly TextureCache _icons;
private static readonly int ListSize = Clans.Count * Genders.Count; private static readonly int ListSize = Clans.Count * Genders.Count;
private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize]; private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize];

View file

@ -14,11 +14,11 @@ namespace Glamourer.GameData;
internal class CustomizeSetFactory( internal class CustomizeSetFactory(
IDataManager _gameData, IDataManager _gameData,
IPluginLog _log, IPluginLog _log,
IconStorage _icons, TextureCache _icons,
NpcCustomizeSet _npcCustomizeSet, NpcCustomizeSet _npcCustomizeSet,
ColorParameters _colors) ColorParameters _colors)
{ {
public CustomizeSetFactory(IDataManager gameData, IPluginLog log, IconStorage icons, NpcCustomizeSet npcCustomizeSet) public CustomizeSetFactory(IDataManager gameData, IPluginLog log, TextureCache icons, NpcCustomizeSet npcCustomizeSet)
: this(gameData, log, icons, npcCustomizeSet, new ColorParameters(gameData, log)) : this(gameData, log, icons, npcCustomizeSet, new ColorParameters(gameData, log))
{ } { }

View file

@ -56,7 +56,7 @@ public unsafe struct NpcData
.Append('-') .Append('-')
.Append(span[i].Variant.Id.ToString("D3")) .Append(span[i].Variant.Id.ToString("D3"))
.Append('-') .Append('-')
.Append(span[i].Stain.Id.ToString("D3")) .Append(span[i].Stains.ToString())
.Append(", "); .Append(", ");
} }
@ -66,7 +66,7 @@ public unsafe struct NpcData
.Append('-') .Append('-')
.Append(Mainhand.Variant.Id.ToString("D3")) .Append(Mainhand.Variant.Id.ToString("D3"))
.Append('-') .Append('-')
.Append(Mainhand.Stain.Id.ToString("D4")) .Append(Mainhand.Stains.ToString())
.Append(", ") .Append(", ")
.Append(Offhand.Skeleton.Id.ToString("D4")) .Append(Offhand.Skeleton.Id.ToString("D4"))
.Append('-') .Append('-')
@ -74,7 +74,7 @@ public unsafe struct NpcData
.Append('-') .Append('-')
.Append(Offhand.Variant.Id.ToString("D3")) .Append(Offhand.Variant.Id.ToString("D3"))
.Append('-') .Append('-')
.Append(Offhand.Stain.Id.ToString("D3")); .Append(Offhand.Stains.ToString());
return sb.ToString(); return sb.ToString();
} }

View file

@ -1,5 +1,6 @@
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures; using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.GameData; using Glamourer.GameData;
@ -23,7 +24,7 @@ public partial class CustomizationDrawer(
: IDisposable : IDisposable
{ {
private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f);
private readonly ISharedImmediateTexture? _legacyTattoo = GetLegacyTattooIcon(pi); private readonly IDalamudTextureWrap? _legacyTattoo = GetLegacyTattooIcon(pi);
private Exception? _terminate; private Exception? _terminate;
@ -191,7 +192,7 @@ public partial class CustomizationDrawer(
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X; _raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
} }
private static ISharedImmediateTexture? GetLegacyTattooIcon(IDalamudPluginInterface pi) private static IDalamudTextureWrap? GetLegacyTattooIcon(IDalamudPluginInterface pi)
{ {
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw"); using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
if (resource == null) if (resource == null)

View file

@ -177,7 +177,8 @@ public unsafe class ModelEvaluationPanel(
{ {
using var id = ImRaii.PushId("Wetness"); using var id = ImRaii.PushId("Wetness");
ImGuiUtil.DrawTableColumn("Wetness"); ImGuiUtil.DrawTableColumn("Wetness");
ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->IsGPoseWet ? "GPose" : "None" : "No Character"); // ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->IsGPoseWet ? "GPose" : "None" : "No Character");
ImGuiUtil.DrawTableColumn(actor.IsCharacter ? "None" : "No Character"); // Until IsGPoseWet is implemented in Penumbra.GameData
var modelString = model.IsCharacterBase var modelString = model.IsCharacterBase
? $"{model.AsCharacterBase->SwimmingWetness:F4} Swimming\n" ? $"{model.AsCharacterBase->SwimmingWetness:F4} Swimming\n"
+ $"{model.AsCharacterBase->WeatherWetness:F4} Weather\n" + $"{model.AsCharacterBase->WeatherWetness:F4} Weather\n"
@ -190,13 +191,19 @@ public unsafe class ModelEvaluationPanel(
return; return;
if (ImGui.SmallButton("GPose On")) if (ImGui.SmallButton("GPose On"))
actor.AsCharacter->IsGPoseWet = true; {
// actor.AsCharacter->IsGPoseWet = true;
}
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.SmallButton("GPose Off")) if (ImGui.SmallButton("GPose Off"))
actor.AsCharacter->IsGPoseWet = false; {
// actor.AsCharacter->IsGPoseWet = false;
}
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.SmallButton("GPose Toggle")) if (ImGui.SmallButton("GPose Toggle"))
actor.AsCharacter->IsGPoseWet = !actor.AsCharacter->IsGPoseWet; {
// actor.AsCharacter->IsGPoseWet = !actor.AsCharacter->IsGPoseWet;
}
} }
private void DrawEquip(Actor actor, Model model) private void DrawEquip(Actor actor, Model model)
@ -214,10 +221,10 @@ public unsafe class ModelEvaluationPanel(
if (ImGui.SmallButton("Change Piece")) if (ImGui.SmallButton("Change Piece"))
_updateSlotService.UpdateArmor(model, slot, _updateSlotService.UpdateArmor(model, slot,
new CharacterArmor((PrimaryId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, 0)); new CharacterArmor((PrimaryId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, new()));
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.SmallButton("Change Stain")) if (ImGui.SmallButton("Change Stain"))
_updateSlotService.UpdateStain(model, slot, 5); _updateSlotService.UpdateStain(model, slot, StainIds.None);
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.SmallButton("Reset")) if (ImGui.SmallButton("Reset"))
_updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot)); _updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot));

View file

@ -3,6 +3,7 @@ using Glamourer.Services;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using System.Diagnostics.Tracing;
namespace Glamourer.Interop.CharaFile; namespace Glamourer.Interop.CharaFile;
@ -61,7 +62,7 @@ public sealed class CmaFile
var armor = ((CharacterArmor*)ptr)[idx]; var armor = ((CharacterArmor*)ptr)[idx];
var item = items.Identify(slot, armor.Set, armor.Variant); var item = items.Identify(slot, armor.Set, armor.Variant);
data.SetItem(slot, item); data.SetItem(slot, item);
data.SetStain(slot, armor.Stain); data.SetStain(slot, armor.Stains);
} }
data.Customize.Read(ptr); data.Customize.Read(ptr);
@ -74,18 +75,18 @@ public sealed class CmaFile
if (mainhand == null) if (mainhand == null)
{ {
data.SetItem(EquipSlot.MainHand, items.DefaultSword); data.SetItem(EquipSlot.MainHand, items.DefaultSword);
data.SetStain(EquipSlot.MainHand, 0); data.SetStain(EquipSlot.MainHand, StainIds.None);
return; return;
} }
var set = mainhand["Item1"]?.ToObject<ushort>() ?? items.DefaultSword.PrimaryId; var set = mainhand["Item1"]?.ToObject<ushort>() ?? items.DefaultSword.PrimaryId;
var type = mainhand["Item2"]?.ToObject<ushort>() ?? items.DefaultSword.SecondaryId; var type = mainhand["Item2"]?.ToObject<ushort>() ?? items.DefaultSword.SecondaryId;
var variant = mainhand["Item3"]?.ToObject<byte>() ?? items.DefaultSword.Variant; var variant = mainhand["Item3"]?.ToObject<byte>() ?? items.DefaultSword.Variant;
var stain = mainhand["Item4"]?.ToObject<byte>() ?? 0; var stains = mainhand["Item4"]?.ToObject<StainIds>() ?? StainIds.None;
var item = items.Identify(EquipSlot.MainHand, set, type, variant); var item = items.Identify(EquipSlot.MainHand, set, type, variant);
data.SetItem(EquipSlot.MainHand, item.Valid ? item : items.DefaultSword); data.SetItem(EquipSlot.MainHand, item.Valid ? item : items.DefaultSword);
data.SetStain(EquipSlot.MainHand, stain); data.SetStain(EquipSlot.MainHand, stains);
} }
private static void ParseOffHand(ItemManager items, JObject jObj, ref DesignData data) private static void ParseOffHand(ItemManager items, JObject jObj, ref DesignData data)

View file

@ -203,7 +203,7 @@ public struct MaterialValueState(
=> DrawData.Skeleton == rhsData.Skeleton => DrawData.Skeleton == rhsData.Skeleton
&& DrawData.Weapon == rhsData.Weapon && DrawData.Weapon == rhsData.Weapon
&& DrawData.Variant == rhsData.Variant && DrawData.Variant == rhsData.Variant
&& DrawData.Stain == rhsData.Stain && DrawData.Stains == rhsData.Stains
&& Game.NearEqual(rhsRow); && Game.NearEqual(rhsRow);
public readonly MaterialValueDesign Convert() public readonly MaterialValueDesign Convert()

View file

@ -54,7 +54,7 @@ public sealed unsafe class PrepareColorSet
return _task.Result.Original(characterBase, material, stainId); return _task.Result.Original(characterBase, material, stainId);
} }
public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId, public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainIds stainIds,
out LegacyColorTable table) out LegacyColorTable table)
{ {
if (material->ColorTable == null) if (material->ColorTable == null)
@ -64,8 +64,8 @@ public sealed unsafe class PrepareColorSet
} }
var newTable = *(LegacyColorTable*)material->ColorTable; var newTable = *(LegacyColorTable*)material->ColorTable;
if (stainId.Id != 0) if (stainIds != StainIds.None)
characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable)); characterBase->ReadStainingTemplate(material, stainIds, (Half*)(&newTable));
table = newTable; table = newTable;
return true; return true;
} }
@ -84,21 +84,21 @@ public sealed unsafe class PrepareColorSet
return false; return false;
} }
return TryGetColorTable(model.AsCharacterBase, handle, GetStain(), out table); return TryGetColorTable(model.AsCharacterBase, handle, GetStains(), out table);
StainId GetStain() StainIds GetStains()
{ {
switch (index.DrawObject) switch (index.DrawObject)
{ {
case MaterialValueIndex.DrawObjectType.Human: case MaterialValueIndex.DrawObjectType.Human:
return index.SlotIndex < 10 ? actor.Model.GetArmor(((uint)index.SlotIndex).ToEquipSlot()).Stain : 0; return index.SlotIndex < 10 ? actor.Model.GetArmor(((uint)index.SlotIndex).ToEquipSlot()).Stains : new();
case MaterialValueIndex.DrawObjectType.Mainhand: case MaterialValueIndex.DrawObjectType.Mainhand:
var mainhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject; var mainhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject;
return mainhand.IsWeapon ? (StainId)mainhand.AsWeapon->ModelUnknown : 0; return mainhand.IsWeapon ? (StainId)mainhand.AsWeapon->ModelUnknown : new();
case MaterialValueIndex.DrawObjectType.Offhand: case MaterialValueIndex.DrawObjectType.Offhand:
var offhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject; var offhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject;
return offhand.IsWeapon ? (StainId)offhand.AsWeapon->ModelUnknown : 0; return offhand.IsWeapon ? (StainId)offhand.AsWeapon->ModelUnknown : new();
default: return 0; default: return new();
} }
} }
} }

View file

@ -1,5 +1,6 @@
using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Interface.Textures;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;

View file

@ -31,14 +31,14 @@ public unsafe class UpdateSlotService : IDisposable
FlagSlotForUpdateInterop(drawObject, slot, data); FlagSlotForUpdateInterop(drawObject, slot, data);
} }
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainId stain) public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainIds stains)
=> UpdateSlot(drawObject, slot, armor.With(stain)); => UpdateSlot(drawObject, slot, armor.With(stains));
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor) public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor)
=> UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stain); => UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stains);
public void UpdateStain(Model drawObject, EquipSlot slot, StainId stain) public void UpdateStain(Model drawObject, EquipSlot slot, StainIds stains)
=> UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stain); => UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stains);
private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data); private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data);

View file

@ -70,7 +70,7 @@ public unsafe class WeaponService : IDisposable
if (tmpWeapon.Value != weapon.Value) if (tmpWeapon.Value != weapon.Value)
{ {
if (tmpWeapon.Skeleton.Id == 0) if (tmpWeapon.Skeleton.Id == 0)
tmpWeapon.Stain = 0; tmpWeapon.Stains = StainIds.None;
_loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4);
} }
@ -107,12 +107,12 @@ public unsafe class WeaponService : IDisposable
} }
} }
public void LoadStain(Actor character, EquipSlot slot, StainId stain) public void LoadStain(Actor character, EquipSlot slot, StainIds stains)
{ {
var mdl = character.Model; var mdl = character.Model;
var (_, _, mh, oh) = mdl.GetWeapons(character); var (_, _, mh, oh) = mdl.GetWeapons(character);
var value = slot == EquipSlot.OffHand ? oh : mh; var value = slot == EquipSlot.OffHand ? oh : mh;
var weapon = value.With(value.Skeleton.Id == 0 ? 0 : stain); var weapon = value.With(value.Skeleton.Id == 0 ? StainIds.None : stains);
LoadWeapon(character, slot, weapon); LoadWeapon(character, slot, weapon);
} }
} }

View file

@ -12,7 +12,7 @@ namespace Glamourer.Services;
public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider) public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider)
: TextureCache(dataManager, textureProvider), IDisposable : TextureCache(dataManager, textureProvider), IDisposable
{ {
private readonly ISharedImmediateTexture?[] _slotIcons = CreateSlotIcons(uiBuilder); private readonly IDalamudTextureWrap?[] _slotIcons = CreateSlotIcons(uiBuilder);
public (nint, Vector2, bool) GetIcon(EquipItem item, EquipSlot slot) public (nint, Vector2, bool) GetIcon(EquipItem item, EquipSlot slot)
{ {
@ -34,9 +34,9 @@ public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager
} }
} }
private static ISharedImmediateTexture?[] CreateSlotIcons(UiBuilder uiBuilder) private static IDalamudTextureWrap?[] CreateSlotIcons(UiBuilder uiBuilder)
{ {
var ret = new ISharedImmediateTexture?[12]; var ret = new IDalamudTextureWrap?[12];
using var uldWrapper = uiBuilder.LoadUld("ui/uld/ArmouryBoard.uld"); using var uldWrapper = uiBuilder.LoadUld("ui/uld/ArmouryBoard.uld");
@ -65,7 +65,7 @@ public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager
{ {
try try
{ {
ret[slot.ToIndex()] = (ISharedImmediateTexture?)uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", index)!; ret[slot.ToIndex()] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", index)!;
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -21,8 +21,8 @@ internal class FunEquipSet
{ {
public Group(ushort headS, byte headV, ushort bodyS, byte bodyV, ushort handsS, byte handsV, ushort legsS, byte legsV, ushort feetS, public Group(ushort headS, byte headV, ushort bodyS, byte bodyV, ushort handsS, byte handsV, ushort legsS, byte legsV, ushort feetS,
byte feetV, StainId[]? stains = null) byte feetV, StainId[]? stains = null)
: this(new CharacterArmor(headS, headV, 0), new CharacterArmor(bodyS, bodyV, 0), new CharacterArmor(handsS, handsV, 0), : this(new CharacterArmor(headS, headV, new()), new CharacterArmor(bodyS, bodyV, new()), new CharacterArmor(handsS, handsV, new()),
new CharacterArmor(legsS, legsV, 0), new CharacterArmor(feetS, feetV, 0), stains) new CharacterArmor(legsS, legsV, new()), new CharacterArmor(feetS, feetV, new()), stains)
{ } { }
public static Group FullSetWithoutHat(ushort modelSet, byte variant, StainId[]? stains = null) public static Group FullSetWithoutHat(ushort modelSet, byte variant, StainId[]? stains = null)

View file

@ -106,7 +106,7 @@ public unsafe class FunModule : IDisposable
&& actor.OnlineStatus is OnlineStatus.PvEMentor or OnlineStatus.PvPMentor or OnlineStatus.TradeMentor && actor.OnlineStatus is OnlineStatus.PvEMentor or OnlineStatus.PvPMentor or OnlineStatus.TradeMentor
&& slot.IsEquipment()) && slot.IsEquipment())
{ {
armor = new CharacterArmor(6117, 1, 0); armor = new CharacterArmor(6117, 1, new());
return; return;
} }
@ -207,8 +207,10 @@ public unsafe class FunModule : IDisposable
private void SetRandomDye(ref CharacterArmor armor) private void SetRandomDye(ref CharacterArmor armor)
{ {
var stainIdx = _rng.Next(0, _stains.Length - 1); var stainIdx1 = _rng.Next(0, _stains.Length - 1);
armor.Stain = _stains[stainIdx]; var stainIdx2 = _rng.Next(0, _stains.Length - 1);
armor.Stains = new(_stains[stainIdx1], _stains[stainIdx2]);
} }
private void SetRandomItem(EquipSlot slot, ref CharacterArmor armor) private void SetRandomItem(EquipSlot slot, ref CharacterArmor armor)
@ -235,17 +237,17 @@ public unsafe class FunModule : IDisposable
private static IReadOnlyList<CharacterArmor> DolphinBodies private static IReadOnlyList<CharacterArmor> DolphinBodies
=> =>
[ [
new CharacterArmor(6089, 1, 4), // Toad new CharacterArmor(6089, 1, new(4, 4)), // Toad
new CharacterArmor(6089, 1, 4), // Toad new CharacterArmor(6089, 1, new(4, 4)), // Toad
new CharacterArmor(6089, 1, 4), // Toad new CharacterArmor(6089, 1, new(4, 4)), // Toad
new CharacterArmor(6023, 1, 4), // Swine new CharacterArmor(6023, 1, new(4, 4)), // Swine
new CharacterArmor(6023, 1, 4), // Swine new CharacterArmor(6023, 1, new(4, 4)), // Swine
new CharacterArmor(6023, 1, 4), // Swine new CharacterArmor(6023, 1, new(4, 4)), // Swine
new CharacterArmor(6133, 1, 4), // Gaja new CharacterArmor(6133, 1, new(4, 4)), // Gaja
new CharacterArmor(6182, 1, 3), // Imp new CharacterArmor(6182, 1, new(3, 3)), // Imp
new CharacterArmor(6182, 1, 3), // Imp new CharacterArmor(6182, 1, new(3, 3)), // Imp
new CharacterArmor(6182, 1, 4), // Imp new CharacterArmor(6182, 1, new(4, 4)), // Imp
new CharacterArmor(6182, 1, 4), // Imp new CharacterArmor(6182, 1, new(4, 4)), // Imp
]; ];
private void SetDolphin(EquipSlot slot, ref CharacterArmor armor) private void SetDolphin(EquipSlot slot, ref CharacterArmor armor)
@ -253,7 +255,7 @@ public unsafe class FunModule : IDisposable
armor = slot switch armor = slot switch
{ {
EquipSlot.Body => DolphinBodies[_rng.Next(0, DolphinBodies.Count - 1)], EquipSlot.Body => DolphinBodies[_rng.Next(0, DolphinBodies.Count - 1)],
EquipSlot.Head => new CharacterArmor(5040, 1, 0), EquipSlot.Head => new CharacterArmor(5040, 1, new(0, 0)),
_ => armor, _ => armor,
}; };
} }
@ -270,7 +272,7 @@ public unsafe class FunModule : IDisposable
private static void SetCrown(Span<CharacterArmor> armor) private static void SetCrown(Span<CharacterArmor> armor)
{ {
var clown = new CharacterArmor(6117, 1, 0); var clown = new CharacterArmor(6117, 1, new());
armor[0] = clown; armor[0] = clown;
armor[1] = clown; armor[1] = clown;
armor[2] = clown; armor[2] = clown;

View file

@ -184,13 +184,13 @@ public class InternalStateEditor(
} }
/// <summary> Change only the stain of an equipment piece. </summary> /// <summary> Change only the stain of an equipment piece. </summary>
public bool ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateSource source, out StainId oldStain, uint key = 0) public bool ChangeStain(ActorState state, EquipSlot slot, StainIds stains, StateSource source, out StainId oldStain, uint key = 0)
{ {
oldStain = state.ModelData.Stain(slot); oldStain = state.ModelData.Stain(slot);
if (!state.CanUnlock(key)) if (!state.CanUnlock(key))
return false; return false;
state.ModelData.SetStain(slot, stain); state.ModelData.SetStain(slot, stains);
state.Sources[slot, true] = source; state.Sources[slot, true] = source;
return true; return true;
} }

View file

@ -130,22 +130,22 @@ public class StateApplier(
/// Change the stain of a single piece of armor or weapon. /// Change the stain of a single piece of armor or weapon.
/// If the offhand is empty, the stain will be fixed to 0 to prevent crashes. /// If the offhand is empty, the stain will be fixed to 0 to prevent crashes.
/// </summary> /// </summary>
public void ChangeStain(ActorData data, EquipSlot slot, StainId stain) public void ChangeStain(ActorData data, EquipSlot slot, StainIds stains)
{ {
var idx = slot.ToIndex(); var idx = slot.ToIndex();
switch (idx) switch (idx)
{ {
case < 10: case < 10:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_updateSlot.UpdateStain(actor.Model, slot, stain); _updateSlot.UpdateStain(actor.Model, slot, stains);
break; break;
case 10: case 10:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadStain(actor, EquipSlot.MainHand, stain); _weapon.LoadStain(actor, EquipSlot.MainHand, stains);
break; break;
case 11: case 11:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadStain(actor, EquipSlot.OffHand, stain); _weapon.LoadStain(actor, EquipSlot.OffHand, stains);
break; break;
} }
} }
@ -162,12 +162,12 @@ public class StateApplier(
/// <summary> Apply a weapon to the appropriate slot. </summary> /// <summary> Apply a weapon to the appropriate slot. </summary>
public void ChangeWeapon(ActorData data, EquipSlot slot, EquipItem item, StainId stain) public void ChangeWeapon(ActorData data, EquipSlot slot, EquipItem item, StainIds stains)
{ {
if (slot is EquipSlot.MainHand) if (slot is EquipSlot.MainHand)
ChangeMainhand(data, item, stain); ChangeMainhand(data, item, stains);
else else
ChangeOffhand(data, item, stain); ChangeOffhand(data, item, stains);
} }
/// <inheritdoc cref="ChangeWeapon(ActorData,EquipSlot,EquipItem,StainId)"/> /// <inheritdoc cref="ChangeWeapon(ActorData,EquipSlot,EquipItem,StainId)"/>
@ -186,19 +186,19 @@ public class StateApplier(
/// <summary> /// <summary>
/// Apply a weapon to the mainhand. If the weapon type has no associated offhand type, apply both. /// Apply a weapon to the mainhand. If the weapon type has no associated offhand type, apply both.
/// </summary> /// </summary>
public void ChangeMainhand(ActorData data, EquipItem weapon, StainId stain) public void ChangeMainhand(ActorData data, EquipItem weapon, StainIds stains)
{ {
var slot = weapon.Type.ValidOffhand() == FullEquipType.Unknown ? EquipSlot.BothHand : EquipSlot.MainHand; var slot = weapon.Type.ValidOffhand() == FullEquipType.Unknown ? EquipSlot.BothHand : EquipSlot.MainHand;
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadWeapon(actor, slot, weapon.Weapon().With(stain)); _weapon.LoadWeapon(actor, slot, weapon.Weapon().With(stains));
} }
/// <summary> Apply a weapon to the offhand. </summary> /// <summary> Apply a weapon to the offhand. </summary>
public void ChangeOffhand(ActorData data, EquipItem weapon, StainId stain) public void ChangeOffhand(ActorData data, EquipItem weapon, StainIds stains)
{ {
stain = weapon.PrimaryId.Id == 0 ? 0 : stain; stains = weapon.PrimaryId.Id == 0 ? StainIds.None : stains;
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain)); _weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stains));
} }
/// <summary> Change a meta state. </summary> /// <summary> Change a meta state. </summary>
@ -209,7 +209,10 @@ public class StateApplier(
case MetaIndex.Wetness: case MetaIndex.Wetness:
{ {
foreach (var actor in data.Objects.Where(a => a.IsCharacter)) foreach (var actor in data.Objects.Where(a => a.IsCharacter))
actor.AsCharacter->IsGPoseWet = value; {
// Disabled until IsGPoseWet implemented in penumbra.gamedata
// actor.AsCharacter->IsGPoseWet = value;
}
return; return;
} }
case MetaIndex.HatState: case MetaIndex.HatState:

View file

@ -90,21 +90,21 @@ public class StateEditor(
} }
/// <inheritdoc/> /// <inheritdoc/>
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings) public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainIds? stains, ApplySettings settings)
{ {
switch (item.HasValue, stain.HasValue) switch (item.HasValue, stains.HasValue)
{ {
case (false, false): return; case (false, false): return;
case (true, false): case (true, false):
ChangeItem(data, slot, item!.Value, settings); ChangeItem(data, slot, item!.Value, settings);
return; return;
case (false, true): case (false, true):
ChangeStain(data, slot, stain!.Value, settings); ChangeStain(data, slot, stains!.Value, settings);
return; return;
} }
var state = (ActorState)data; var state = (ActorState)data;
if (!Editor.ChangeEquip(state, slot, item ?? state.ModelData.Item(slot), stain ?? state.ModelData.Stain(slot), settings.Source, if (!Editor.ChangeEquip(state, slot, item ?? state.ModelData.Item(slot), stains ?? state.ModelData.Stain(slot), settings.Source,
out var old, out var oldStain, settings.Key)) out var old, out var oldStain, settings.Key))
return; return;
@ -115,25 +115,25 @@ public class StateEditor(
item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
if (slot is EquipSlot.MainHand) if (slot is EquipSlot.MainHand)
ApplyMainhandPeriphery(state, item, stain, settings); ApplyMainhandPeriphery(state, item, stains, settings);
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStain.Id} to {stain!.Value.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStain.Id} to {stain!.Value.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(type, settings.Source, state, actors, (old, item!.Value, slot)); StateChanged.Invoke(type, settings.Source, state, actors, (old, item!.Value, slot));
StateChanged.Invoke(StateChangeType.Stain, settings.Source, state, actors, (oldStain, stain!.Value, slot)); StateChanged.Invoke(StateChangeType.Stain, settings.Source, state, actors, (oldStain, stains!.Value, slot));
} }
/// <inheritdoc/> /// <inheritdoc/>
public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings) public void ChangeStain(object data, EquipSlot slot, StainIds stains, ApplySettings settings)
{ {
var state = (ActorState)data; var state = (ActorState)data;
if (!Editor.ChangeStain(state, slot, stain, settings.Source, out var old, settings.Key)) if (!Editor.ChangeStain(state, slot, stains, settings.Source, out var old, settings.Key))
return; return;
var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange());
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stains.ToString()}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChangeType.Stain, settings.Source, state, actors, (old, stain, slot)); StateChanged.Invoke(StateChangeType.Stain, settings.Source, state, actors, (old, stains, slot));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -392,19 +392,24 @@ public class StateEditor(
/// <summary> Apply offhand item and potentially gauntlets if configured. </summary> /// <summary> Apply offhand item and potentially gauntlets if configured. </summary>
private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, StainId? newStain, ApplySettings settings) private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, StainIds? newStains, ApplySettings settings)
{ {
if (!Config.ChangeEntireItem || !settings.Source.IsManual()) if (!Config.ChangeEntireItem || !settings.Source.IsManual())
return; return;
var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand);
var offhand = newMainhand != null ? Items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand); var offhand = newMainhand != null ? Items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand);
var stain = newStain ?? state.ModelData.Stain(EquipSlot.MainHand); var stains = newStains ?? state.ModelData.Stain(EquipSlot.MainHand);
if (offhand.Valid) if (offhand.Valid)
ChangeEquip(state, EquipSlot.OffHand, offhand, stain, settings); ChangeEquip(state, EquipSlot.OffHand, offhand, stains, settings);
if (mh is { Type: FullEquipType.Fists } && Items.ItemData.Tertiary.TryGetValue(mh.ItemId, out var gauntlets)) if (mh is { Type: FullEquipType.Fists } && Items.ItemData.Tertiary.TryGetValue(mh.ItemId, out var gauntlets))
ChangeEquip(state, EquipSlot.Hands, newMainhand != null ? gauntlets : state.ModelData.Item(EquipSlot.Hands), ChangeEquip(state, EquipSlot.Hands, newMainhand != null ? gauntlets : state.ModelData.Item(EquipSlot.Hands),
stain, settings); stains, settings);
}
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings = default)
{
throw new NotImplementedException();
} }
} }

View file

@ -250,14 +250,14 @@ public class StateListener : IDisposable
&& current.Weapon == changed.Weapon && current.Weapon == changed.Weapon
&& !state.Sources[slot, false].IsFixed(); && !state.Sources[slot, false].IsFixed();
var stainChanged = current.Stain == changed.Stain && !state.Sources[slot, true].IsFixed(); var stainChanged = current.Stains == changed.Stains && !state.Sources[slot, true].IsFixed();
switch ((itemChanged, stainChanged)) switch ((itemChanged, stainChanged))
{ {
case (true, true): case (true, true):
_manager.ChangeEquip(state, slot, currentItem, current.Stain, ApplySettings.Game); _manager.ChangeEquip(state, slot, currentItem, current.Stains, ApplySettings.Game);
if (slot is EquipSlot.MainHand or EquipSlot.OffHand) if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
_applier.ChangeWeapon(objects, slot, currentItem, current.Stain); _applier.ChangeWeapon(objects, slot, currentItem, current.Stains);
else else
_applier.ChangeArmor(objects, slot, current.ToArmor(), !state.Sources[slot, false].IsFixed(), _applier.ChangeArmor(objects, slot, current.ToArmor(), !state.Sources[slot, false].IsFixed(),
state.ModelData.IsHatVisible()); state.ModelData.IsHatVisible());
@ -265,14 +265,14 @@ public class StateListener : IDisposable
case (true, false): case (true, false):
_manager.ChangeItem(state, slot, currentItem, ApplySettings.Game); _manager.ChangeItem(state, slot, currentItem, ApplySettings.Game);
if (slot is EquipSlot.MainHand or EquipSlot.OffHand) if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
_applier.ChangeWeapon(objects, slot, currentItem, model.Stain); _applier.ChangeWeapon(objects, slot, currentItem, model.Stains);
else else
_applier.ChangeArmor(objects, slot, current.ToArmor(model.Stain), !state.Sources[slot, false].IsFixed(), _applier.ChangeArmor(objects, slot, current.ToArmor(model.Stains), !state.Sources[slot, false].IsFixed(),
state.ModelData.IsHatVisible()); state.ModelData.IsHatVisible());
break; break;
case (false, true): case (false, true):
_manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game); _manager.ChangeStain(state, slot, current.Stains, ApplySettings.Game);
_applier.ChangeStain(objects, slot, current.Stain); _applier.ChangeStain(objects, slot, current.Stains);
break; break;
} }
} }
@ -332,7 +332,7 @@ public class StateListener : IDisposable
else else
{ {
if (weapon.Skeleton.Id != 0) if (weapon.Skeleton.Id != 0)
weapon = weapon.With(newWeapon.Stain); weapon = weapon.With(newWeapon.Stains);
// Force unlock if necessary. // Force unlock if necessary.
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game with { Key = state.Combination }); _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game with { Key = state.Combination });
} }
@ -341,7 +341,7 @@ public class StateListener : IDisposable
// Fist Weapon Offhand hack. // Fist Weapon Offhand hack.
if (slot is EquipSlot.MainHand && weapon.Skeleton.Id is > 1600 and < 1651) if (slot is EquipSlot.MainHand && weapon.Skeleton.Id is > 1600 and < 1651)
_lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant,
weapon.Stain); weapon.Stains);
_funModule.ApplyFunToWeapon(actor, ref weapon, slot); _funModule.ApplyFunToWeapon(actor, ref weapon, slot);
} }
@ -365,7 +365,7 @@ public class StateListener : IDisposable
{ {
var item = _items.Identify(slot, actorArmor.Set, actorArmor.Variant); var item = _items.Identify(slot, actorArmor.Set, actorArmor.Variant);
state.BaseData.SetItem(EquipSlot.Head, item); state.BaseData.SetItem(EquipSlot.Head, item);
state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stain); state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stains);
return UpdateState.Change; return UpdateState.Change;
} }
@ -378,9 +378,9 @@ public class StateListener : IDisposable
var baseData = state.BaseData.Armor(slot); var baseData = state.BaseData.Armor(slot);
var change = UpdateState.NoChange; var change = UpdateState.NoChange;
if (baseData.Stain != armor.Stain) if (baseData.Stains != armor.Stains)
{ {
state.BaseData.SetStain(slot, armor.Stain); state.BaseData.SetStain(slot, armor.Stains);
change = UpdateState.Change; change = UpdateState.Change;
} }
@ -503,9 +503,9 @@ public class StateListener : IDisposable
if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().Skeleton.Id is > 1600 and < 1651) if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().Skeleton.Id is > 1600 and < 1651)
return UpdateState.NoChange; return UpdateState.NoChange;
if (baseData.Stain != weapon.Stain) if (baseData.Stains != weapon.Stains)
{ {
state.BaseData.SetStain(slot, weapon.Stain); state.BaseData.SetStain(slot, weapon.Stains);
change = UpdateState.Change; change = UpdateState.Change;
} }

View file

@ -141,7 +141,7 @@ public sealed class StateManager(
var head = ret.IsHatVisible() || ignoreHatState ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head); var head = ret.IsHatVisible() || ignoreHatState ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head);
var headItem = Items.Identify(EquipSlot.Head, head.Set, head.Variant); var headItem = Items.Identify(EquipSlot.Head, head.Set, head.Variant);
ret.SetItem(EquipSlot.Head, headItem); ret.SetItem(EquipSlot.Head, headItem);
ret.SetStain(EquipSlot.Head, head.Stain); ret.SetStain(EquipSlot.Head, head.Stains);
// The other slots can be used from the draw object. // The other slots can be used from the draw object.
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1)) foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
@ -149,7 +149,7 @@ public sealed class StateManager(
var armor = model.GetArmor(slot); var armor = model.GetArmor(slot);
var item = Items.Identify(slot, armor.Set, armor.Variant); var item = Items.Identify(slot, armor.Set, armor.Variant);
ret.SetItem(slot, item); ret.SetItem(slot, item);
ret.SetStain(slot, armor.Stain); ret.SetStain(slot, armor.Stains);
} }
// Weapons use the draw objects of the weapons, but require the game object either way. // Weapons use the draw objects of the weapons, but require the game object either way.
@ -171,7 +171,7 @@ public sealed class StateManager(
var armor = actor.GetArmor(slot); var armor = actor.GetArmor(slot);
var item = Items.Identify(slot, armor.Set, armor.Variant); var item = Items.Identify(slot, armor.Set, armor.Variant);
ret.SetItem(slot, item); ret.SetItem(slot, item);
ret.SetStain(slot, armor.Stain); ret.SetStain(slot, armor.Stains);
} }
main = actor.GetMainhand(); main = actor.GetMainhand();
@ -187,13 +187,15 @@ public sealed class StateManager(
var mainItem = Items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant); var mainItem = Items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant);
var offItem = Items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type); var offItem = Items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type);
ret.SetItem(EquipSlot.MainHand, mainItem); ret.SetItem(EquipSlot.MainHand, mainItem);
ret.SetStain(EquipSlot.MainHand, main.Stain); ret.SetStain(EquipSlot.MainHand, main.Stains);
ret.SetItem(EquipSlot.OffHand, offItem); ret.SetItem(EquipSlot.OffHand, offItem);
ret.SetStain(EquipSlot.OffHand, off.Stain); ret.SetStain(EquipSlot.OffHand, off.Stains);
// Wetness can technically only be set in GPose or via external tools. // Wetness can technically only be set in GPose or via external tools.
// It is only available in the game object. // It is only available in the game object.
ret.SetIsWet(actor.AsCharacter->IsGPoseWet);
// Disabled until IsGPoseWet is implemented in Penumbra.GameData
// ret.SetIsWet(actor.AsCharacter->IsGPoseWet);
// Weapon visibility could technically be inferred from the weapon draw objects, // Weapon visibility could technically be inferred from the weapon draw objects,
// but since we use hat visibility from the game object we can also use weapon visibility from it. // but since we use hat visibility from the game object we can also use weapon visibility from it.
@ -214,7 +216,7 @@ public sealed class StateManager(
offhand.Variant = mainhand.Variant; offhand.Variant = mainhand.Variant;
offhand.Weapon = mainhand.Weapon; offhand.Weapon = mainhand.Weapon;
ret.SetItem(EquipSlot.Hands, gauntlets); ret.SetItem(EquipSlot.Hands, gauntlets);
ret.SetStain(EquipSlot.Hands, mainhand.Stain); ret.SetStain(EquipSlot.Hands, mainhand.Stains);
} }
/// <summary> Turn an actor human. </summary> /// <summary> Turn an actor human. </summary>