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

View file

@ -194,7 +194,7 @@ public class DesignConverter(
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);
@ -204,7 +204,7 @@ public class DesignConverter(
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);
if (!skipWarnings && !oh.Valid)
@ -215,7 +215,7 @@ public class DesignConverter(
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,

View file

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

View file

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

View file

@ -14,11 +14,11 @@ namespace Glamourer.GameData;
internal class CustomizeSetFactory(
IDataManager _gameData,
IPluginLog _log,
IconStorage _icons,
TextureCache _icons,
NpcCustomizeSet _npcCustomizeSet,
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))
{ }

View file

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

View file

@ -1,5 +1,6 @@
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility;
using Dalamud.Plugin;
using Glamourer.GameData;
@ -23,7 +24,7 @@ public partial class CustomizationDrawer(
: IDisposable
{
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;
@ -191,7 +192,7 @@ public partial class CustomizationDrawer(
_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");
if (resource == null)

View file

@ -177,7 +177,8 @@ public unsafe class ModelEvaluationPanel(
{
using var id = ImRaii.PushId("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
? $"{model.AsCharacterBase->SwimmingWetness:F4} Swimming\n"
+ $"{model.AsCharacterBase->WeatherWetness:F4} Weather\n"
@ -190,13 +191,19 @@ public unsafe class ModelEvaluationPanel(
return;
if (ImGui.SmallButton("GPose On"))
actor.AsCharacter->IsGPoseWet = true;
{
// actor.AsCharacter->IsGPoseWet = true;
}
ImGui.SameLine();
if (ImGui.SmallButton("GPose Off"))
actor.AsCharacter->IsGPoseWet = false;
{
// actor.AsCharacter->IsGPoseWet = false;
}
ImGui.SameLine();
if (ImGui.SmallButton("GPose Toggle"))
actor.AsCharacter->IsGPoseWet = !actor.AsCharacter->IsGPoseWet;
{
// actor.AsCharacter->IsGPoseWet = !actor.AsCharacter->IsGPoseWet;
}
}
private void DrawEquip(Actor actor, Model model)
@ -214,10 +221,10 @@ public unsafe class ModelEvaluationPanel(
if (ImGui.SmallButton("Change Piece"))
_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();
if (ImGui.SmallButton("Change Stain"))
_updateSlotService.UpdateStain(model, slot, 5);
_updateSlotService.UpdateStain(model, slot, StainIds.None);
ImGui.SameLine();
if (ImGui.SmallButton("Reset"))
_updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot));

View file

@ -3,6 +3,7 @@ using Glamourer.Services;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using System.Diagnostics.Tracing;
namespace Glamourer.Interop.CharaFile;
@ -61,7 +62,7 @@ public sealed class CmaFile
var armor = ((CharacterArmor*)ptr)[idx];
var item = items.Identify(slot, armor.Set, armor.Variant);
data.SetItem(slot, item);
data.SetStain(slot, armor.Stain);
data.SetStain(slot, armor.Stains);
}
data.Customize.Read(ptr);
@ -74,18 +75,18 @@ public sealed class CmaFile
if (mainhand == null)
{
data.SetItem(EquipSlot.MainHand, items.DefaultSword);
data.SetStain(EquipSlot.MainHand, 0);
data.SetStain(EquipSlot.MainHand, StainIds.None);
return;
}
var set = mainhand["Item1"]?.ToObject<ushort>() ?? items.DefaultSword.PrimaryId;
var type = mainhand["Item2"]?.ToObject<ushort>() ?? items.DefaultSword.SecondaryId;
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);
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)

View file

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

View file

@ -54,7 +54,7 @@ public sealed unsafe class PrepareColorSet
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)
{
if (material->ColorTable == null)
@ -64,8 +64,8 @@ public sealed unsafe class PrepareColorSet
}
var newTable = *(LegacyColorTable*)material->ColorTable;
if (stainId.Id != 0)
characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable));
if (stainIds != StainIds.None)
characterBase->ReadStainingTemplate(material, stainIds, (Half*)(&newTable));
table = newTable;
return true;
}
@ -84,21 +84,21 @@ public sealed unsafe class PrepareColorSet
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)
{
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:
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:
var offhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject;
return offhand.IsWeapon ? (StainId)offhand.AsWeapon->ModelUnknown : 0;
default: return 0;
return offhand.IsWeapon ? (StainId)offhand.AsWeapon->ModelUnknown : new();
default: return new();
}
}
}

View file

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

View file

@ -31,14 +31,14 @@ public unsafe class UpdateSlotService : IDisposable
FlagSlotForUpdateInterop(drawObject, slot, data);
}
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainId stain)
=> UpdateSlot(drawObject, slot, armor.With(stain));
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainIds stains)
=> UpdateSlot(drawObject, slot, armor.With(stains));
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)
=> UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stain);
public void UpdateStain(Model drawObject, EquipSlot slot, StainIds stains)
=> UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stains);
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.Skeleton.Id == 0)
tmpWeapon.Stain = 0;
tmpWeapon.Stains = StainIds.None;
_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 (_, _, mh, oh) = mdl.GetWeapons(character);
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);
}
}

View file

@ -12,7 +12,7 @@ namespace Glamourer.Services;
public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider)
: 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)
{
@ -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");
@ -65,7 +65,7 @@ public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager
{
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)
{

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,
byte feetV, StainId[]? stains = null)
: this(new CharacterArmor(headS, headV, 0), new CharacterArmor(bodyS, bodyV, 0), new CharacterArmor(handsS, handsV, 0),
new CharacterArmor(legsS, legsV, 0), new CharacterArmor(feetS, feetV, 0), stains)
: this(new CharacterArmor(headS, headV, new()), new CharacterArmor(bodyS, bodyV, new()), new CharacterArmor(handsS, handsV, new()),
new CharacterArmor(legsS, legsV, new()), new CharacterArmor(feetS, feetV, new()), stains)
{ }
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
&& slot.IsEquipment())
{
armor = new CharacterArmor(6117, 1, 0);
armor = new CharacterArmor(6117, 1, new());
return;
}
@ -207,8 +207,10 @@ public unsafe class FunModule : IDisposable
private void SetRandomDye(ref CharacterArmor armor)
{
var stainIdx = _rng.Next(0, _stains.Length - 1);
armor.Stain = _stains[stainIdx];
var stainIdx1 = _rng.Next(0, _stains.Length - 1);
var stainIdx2 = _rng.Next(0, _stains.Length - 1);
armor.Stains = new(_stains[stainIdx1], _stains[stainIdx2]);
}
private void SetRandomItem(EquipSlot slot, ref CharacterArmor armor)
@ -235,17 +237,17 @@ public unsafe class FunModule : IDisposable
private static IReadOnlyList<CharacterArmor> DolphinBodies
=>
[
new CharacterArmor(6089, 1, 4), // Toad
new CharacterArmor(6089, 1, 4), // Toad
new CharacterArmor(6089, 1, 4), // Toad
new CharacterArmor(6023, 1, 4), // Swine
new CharacterArmor(6023, 1, 4), // Swine
new CharacterArmor(6023, 1, 4), // Swine
new CharacterArmor(6133, 1, 4), // Gaja
new CharacterArmor(6182, 1, 3), // Imp
new CharacterArmor(6182, 1, 3), // Imp
new CharacterArmor(6182, 1, 4), // Imp
new CharacterArmor(6182, 1, 4), // Imp
new CharacterArmor(6089, 1, new(4, 4)), // Toad
new CharacterArmor(6089, 1, new(4, 4)), // Toad
new CharacterArmor(6089, 1, new(4, 4)), // Toad
new CharacterArmor(6023, 1, new(4, 4)), // Swine
new CharacterArmor(6023, 1, new(4, 4)), // Swine
new CharacterArmor(6023, 1, new(4, 4)), // Swine
new CharacterArmor(6133, 1, new(4, 4)), // Gaja
new CharacterArmor(6182, 1, new(3, 3)), // Imp
new CharacterArmor(6182, 1, new(3, 3)), // Imp
new CharacterArmor(6182, 1, new(4, 4)), // Imp
new CharacterArmor(6182, 1, new(4, 4)), // Imp
];
private void SetDolphin(EquipSlot slot, ref CharacterArmor armor)
@ -253,7 +255,7 @@ public unsafe class FunModule : IDisposable
armor = slot switch
{
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,
};
}
@ -270,7 +272,7 @@ public unsafe class FunModule : IDisposable
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[1] = clown;
armor[2] = clown;

View file

@ -184,13 +184,13 @@ public class InternalStateEditor(
}
/// <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);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetStain(slot, stain);
state.ModelData.SetStain(slot, stains);
state.Sources[slot, true] = source;
return true;
}

View file

@ -130,22 +130,22 @@ public class StateApplier(
/// 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.
/// </summary>
public void ChangeStain(ActorData data, EquipSlot slot, StainId stain)
public void ChangeStain(ActorData data, EquipSlot slot, StainIds stains)
{
var idx = slot.ToIndex();
switch (idx)
{
case < 10:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_updateSlot.UpdateStain(actor.Model, slot, stain);
_updateSlot.UpdateStain(actor.Model, slot, stains);
break;
case 10:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadStain(actor, EquipSlot.MainHand, stain);
_weapon.LoadStain(actor, EquipSlot.MainHand, stains);
break;
case 11:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadStain(actor, EquipSlot.OffHand, stain);
_weapon.LoadStain(actor, EquipSlot.OffHand, stains);
break;
}
}
@ -162,12 +162,12 @@ public class StateApplier(
/// <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)
ChangeMainhand(data, item, stain);
ChangeMainhand(data, item, stains);
else
ChangeOffhand(data, item, stain);
ChangeOffhand(data, item, stains);
}
/// <inheritdoc cref="ChangeWeapon(ActorData,EquipSlot,EquipItem,StainId)"/>
@ -186,19 +186,19 @@ public class StateApplier(
/// <summary>
/// Apply a weapon to the mainhand. If the weapon type has no associated offhand type, apply both.
/// </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;
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>
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))
_weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain));
_weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stains));
}
/// <summary> Change a meta state. </summary>
@ -209,7 +209,10 @@ public class StateApplier(
case MetaIndex.Wetness:
{
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;
}
case MetaIndex.HatState:

View file

@ -90,21 +90,21 @@ public class StateEditor(
}
/// <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 (true, false):
ChangeItem(data, slot, item!.Value, settings);
return;
case (false, true):
ChangeStain(data, slot, stain!.Value, settings);
ChangeStain(data, slot, stains!.Value, settings);
return;
}
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))
return;
@ -115,25 +115,25 @@ public class StateEditor(
item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
if (slot is EquipSlot.MainHand)
ApplyMainhandPeriphery(state, item, stain, settings);
ApplyMainhandPeriphery(state, item, stains, settings);
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")}.]");
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/>
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;
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;
var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange());
Glamourer.Log.Verbose(
$"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChangeType.Stain, settings.Source, state, actors, (old, stain, slot));
$"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, stains, slot));
}
/// <inheritdoc/>
@ -392,19 +392,24 @@ public class StateEditor(
/// <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())
return;
var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand);
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)
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))
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
&& !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))
{
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)
_applier.ChangeWeapon(objects, slot, currentItem, current.Stain);
_applier.ChangeWeapon(objects, slot, currentItem, current.Stains);
else
_applier.ChangeArmor(objects, slot, current.ToArmor(), !state.Sources[slot, false].IsFixed(),
state.ModelData.IsHatVisible());
@ -265,14 +265,14 @@ public class StateListener : IDisposable
case (true, false):
_manager.ChangeItem(state, slot, currentItem, ApplySettings.Game);
if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
_applier.ChangeWeapon(objects, slot, currentItem, model.Stain);
_applier.ChangeWeapon(objects, slot, currentItem, model.Stains);
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());
break;
case (false, true):
_manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game);
_applier.ChangeStain(objects, slot, current.Stain);
_manager.ChangeStain(state, slot, current.Stains, ApplySettings.Game);
_applier.ChangeStain(objects, slot, current.Stains);
break;
}
}
@ -332,7 +332,7 @@ public class StateListener : IDisposable
else
{
if (weapon.Skeleton.Id != 0)
weapon = weapon.With(newWeapon.Stain);
weapon = weapon.With(newWeapon.Stains);
// Force unlock if necessary.
_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.
if (slot is EquipSlot.MainHand && weapon.Skeleton.Id is > 1600 and < 1651)
_lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant,
weapon.Stain);
weapon.Stains);
_funModule.ApplyFunToWeapon(actor, ref weapon, slot);
}
@ -365,7 +365,7 @@ public class StateListener : IDisposable
{
var item = _items.Identify(slot, actorArmor.Set, actorArmor.Variant);
state.BaseData.SetItem(EquipSlot.Head, item);
state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stain);
state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stains);
return UpdateState.Change;
}
@ -378,9 +378,9 @@ public class StateListener : IDisposable
var baseData = state.BaseData.Armor(slot);
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;
}
@ -503,9 +503,9 @@ public class StateListener : IDisposable
if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().Skeleton.Id is > 1600 and < 1651)
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;
}

View file

@ -141,7 +141,7 @@ public sealed class StateManager(
var head = ret.IsHatVisible() || ignoreHatState ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head);
var headItem = Items.Identify(EquipSlot.Head, head.Set, head.Variant);
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.
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
@ -149,7 +149,7 @@ public sealed class StateManager(
var armor = model.GetArmor(slot);
var item = Items.Identify(slot, armor.Set, armor.Variant);
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.
@ -171,7 +171,7 @@ public sealed class StateManager(
var armor = actor.GetArmor(slot);
var item = Items.Identify(slot, armor.Set, armor.Variant);
ret.SetItem(slot, item);
ret.SetStain(slot, armor.Stain);
ret.SetStain(slot, armor.Stains);
}
main = actor.GetMainhand();
@ -187,13 +187,15 @@ public sealed class StateManager(
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);
ret.SetItem(EquipSlot.MainHand, mainItem);
ret.SetStain(EquipSlot.MainHand, main.Stain);
ret.SetStain(EquipSlot.MainHand, main.Stains);
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.
// 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,
// 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.Weapon = mainhand.Weapon;
ret.SetItem(EquipSlot.Hands, gauntlets);
ret.SetStain(EquipSlot.Hands, mainhand.Stain);
ret.SetStain(EquipSlot.Hands, mainhand.Stains);
}
/// <summary> Turn an actor human. </summary>