Initial Update for multiple stains, some facewear support, and API X

This commit is contained in:
Ottermandias 2024-07-11 14:21:25 +02:00
parent c1d9af2dd0
commit 7caf6cc08a
90 changed files with 654 additions and 537 deletions

View file

@ -1,5 +1,4 @@
using Glamourer.Interop.Structs;
using Penumbra.GameData.Structs;
using Penumbra.GameData.Structs;
namespace Glamourer.State;
@ -21,8 +20,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, StainIds.None), new CharacterArmor(bodyS, bodyV, StainIds.None), new CharacterArmor(handsS, handsV, StainIds.None),
new CharacterArmor(legsS, legsV, StainIds.None), new CharacterArmor(feetS, feetV, StainIds.None), stains)
{ }
public static Group FullSetWithoutHat(ushort modelSet, byte variant, StainId[]? stains = null)

View file

@ -1,5 +1,5 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.ImGuiNotification;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Designs;
using Glamourer.Gui;
using Glamourer.Services;
@ -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, StainIds.None);
return;
}
@ -171,7 +171,7 @@ public unsafe class FunModule : IDisposable
break;
case CodeService.CodeFlag.Dolphins:
SetDolphin(EquipSlot.Body, ref armor[1]);
SetDolphin(EquipSlot.Head, ref armor[0]);
SetDolphin(EquipSlot.Head, ref armor[0]);
break;
case CodeService.CodeFlag.World when actor.Index != 0:
_worldSets.Apply(actor, _rng, armor);
@ -198,7 +198,7 @@ public unsafe class FunModule : IDisposable
private static bool ValidFunTarget(Actor actor)
=> actor.IsCharacter
&& actor.AsObject->ObjectKind is (byte)ObjectKind.Player
&& actor.AsObject->ObjectKind is ObjectKind.Pc
&& !actor.IsTransformed
&& actor.AsCharacter->CharacterData.ModelCharaId == 0;
@ -208,7 +208,7 @@ public unsafe class FunModule : IDisposable
private void SetRandomDye(ref CharacterArmor armor)
{
var stainIdx = _rng.Next(0, _stains.Length - 1);
armor.Stain = _stains[stainIdx];
armor.Stains = _stains[stainIdx];
}
private void SetRandomItem(EquipSlot slot, ref CharacterArmor armor)
@ -235,17 +235,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 StainIds(4)), // Toad
new CharacterArmor(6089, 1, new StainIds(4)), // Toad
new CharacterArmor(6089, 1, new StainIds(4)), // Toad
new CharacterArmor(6023, 1, new StainIds(4)), // Swine
new CharacterArmor(6023, 1, new StainIds(4)), // Swine
new CharacterArmor(6023, 1, new StainIds(4)), // Swine
new CharacterArmor(6133, 1, new StainIds(4)), // Gaja
new CharacterArmor(6182, 1, new StainIds(3)), // Imp
new CharacterArmor(6182, 1, new StainIds(3)), // Imp
new CharacterArmor(6182, 1, new StainIds(4)), // Imp
new CharacterArmor(6182, 1, new StainIds(4)), // Imp
];
private void SetDolphin(EquipSlot slot, ref CharacterArmor armor)
@ -253,7 +253,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, StainIds.None),
_ => armor,
};
}
@ -270,7 +270,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, StainIds.None);
armor[0] = clown;
armor[1] = clown;
armor[2] = clown;
@ -285,15 +285,12 @@ public unsafe class FunModule : IDisposable
return;
var targetClan = (SubRace)((int)race * 2 - (int)customize.Clan % 2);
// TODO Female Hrothgar
if (race is Race.Hrothgar && customize.Gender is Gender.Female)
targetClan = targetClan is SubRace.Lost ? SubRace.Seawolf : SubRace.Hellsguard;
_customizations.ChangeClan(ref customize, targetClan);
}
private void SetGender(ref CustomizeArray customize)
{
if (!_codes.Enabled(CodeService.CodeFlag.SixtyThree) || customize.Race is Race.Hrothgar) // TODO Female Hrothgar
if (!_codes.Enabled(CodeService.CodeFlag.SixtyThree))
return;
_customizations.ChangeGender(ref customize, customize.Gender is Gender.Male ? Gender.Female : Gender.Male);

View file

@ -152,11 +152,11 @@ public class InternalStateEditor(
}
/// <summary> Change a single piece of equipment including stain. </summary>
public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateSource source, out EquipItem oldItem,
out StainId oldStain, uint key = 0)
public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainIds stains, StateSource source, out EquipItem oldItem,
out StainIds oldStains, uint key = 0)
{
oldItem = state.ModelData.Item(slot);
oldStain = state.ModelData.Stain(slot);
oldStains = state.ModelData.Stain(slot);
if (!state.CanUnlock(key))
return false;
@ -168,7 +168,7 @@ public class InternalStateEditor(
return false;
var old = oldItem;
var oldS = oldStain;
var oldS = oldStains;
gPose.AddActionOnLeave(() =>
{
if (old.Type == state.BaseData.Item(slot).Type)
@ -177,20 +177,20 @@ public class InternalStateEditor(
}
state.ModelData.SetItem(slot, item);
state.ModelData.SetStain(slot, stain);
state.ModelData.SetStain(slot, stains);
state.Sources[slot, false] = source;
state.Sources[slot, true] = source;
return true;
}
/// <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 ChangeStains(ActorState state, EquipSlot slot, StainIds stains, StateSource source, out StainIds oldStains, uint key = 0)
{
oldStain = state.ModelData.Stain(slot);
oldStains = 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,7 @@ public class StateApplier(
case MetaIndex.Wetness:
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
actor.AsCharacter->IsGPoseWet = value;
actor.IsGPoseWet = value;
return;
}
case MetaIndex.HatState:

View file

@ -90,22 +90,22 @@ 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);
ChangeStains(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,
out var old, out var oldStain, settings.Key))
if (!Editor.ChangeEquip(state, slot, item ?? state.ModelData.Item(slot), stains ?? state.ModelData.Stain(slot), settings.Source,
out var old, out var oldStains, settings.Key))
return;
var type = slot.ToIndex() < 10 ? StateChangeType.Equip : StateChangeType.Weapon;
@ -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));
$"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 {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(type, settings.Source, state, actors, (old, item!.Value, slot));
StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, (oldStains, stains!.Value, slot));
}
/// <inheritdoc/>
public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings)
public void ChangeStains(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.ChangeStains(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} to {stains}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, (old, stains, slot));
}
/// <inheritdoc/>
@ -269,7 +269,7 @@ public class StateEditor(
if (mergedDesign.Design.DoApplyStain(slot))
if (!settings.RespectManual || !state.Sources[slot, true].IsManual())
Editor.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot),
Editor.ChangeStains(state, slot, mergedDesign.Design.DesignData.Stain(slot),
Source(slot.ToState(true)), out _, settings.Key);
}
@ -277,7 +277,7 @@ public class StateEditor(
{
if (mergedDesign.Design.DoApplyStain(weaponSlot))
if (!settings.RespectManual || !state.Sources[weaponSlot, true].IsManual())
Editor.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot),
Editor.ChangeStains(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot),
Source(weaponSlot.ToState(true)), out _, settings.Key);
if (!mergedDesign.Design.DoApplyEquip(weaponSlot))
@ -392,19 +392,19 @@ 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);
}
}

View file

@ -227,7 +227,7 @@ public class StateListener : IDisposable
(_, armor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
}
private void OnMovedEquipment((EquipSlot, uint, StainId)[] items)
private void OnMovedEquipment((EquipSlot, uint, StainIds)[] items)
{
_objects.Update();
var (identifier, objects) = _objects.PlayerData;
@ -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.ChangeStains(state, slot, current.Stains, ApplySettings.Game);
_applier.ChangeStain(objects, slot, current.Stains);
break;
}
}
@ -308,7 +308,7 @@ public class StateListener : IDisposable
apply = true;
if (!state.Sources[slot, true].IsFixed())
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
_manager.ChangeStains(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
else
apply = true;
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;
}
@ -418,7 +418,7 @@ public class StateListener : IDisposable
apply = true;
if (!state.Sources[slot, true].IsFixed())
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
_manager.ChangeStains(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
else
apply = true;
@ -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

@ -117,7 +117,7 @@ public sealed class StateManager(
if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
{
ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData,
(nint)(&actor.AsCharacter->DrawData.Head));
(nint)Unsafe.AsPointer(ref actor.AsCharacter->DrawData.EquipmentModelIds[0]));
return ret;
}
@ -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,13 @@ 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);
ret.SetIsWet(actor.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 +214,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>
@ -414,7 +414,9 @@ public sealed class StateManager(
public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source)
{
var data = Applier.ApplyAll(state,
forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false);
forceRedraw
|| !actor.Model.IsHuman
|| CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false);
StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null);
}

View file

@ -22,7 +22,7 @@ public class WorldSets
[(Gender.Male, Race.AuRa)] = FunEquipSet.Group.FullSetWithoutHat(0257, 2),
[(Gender.Female, Race.AuRa)] = FunEquipSet.Group.FullSetWithoutHat(0258, 2),
[(Gender.Male, Race.Hrothgar)] = FunEquipSet.Group.FullSetWithoutHat(0597, 1),
[(Gender.Female, Race.Hrothgar)] = FunEquipSet.Group.FullSetWithoutHat(0000, 0), // TODO Hrothgar Female
[(Gender.Female, Race.Hrothgar)] = FunEquipSet.Group.FullSetWithoutHat(0829, 1),
[(Gender.Male, Race.Viera)] = FunEquipSet.Group.FullSetWithoutHat(0744, 1),
[(Gender.Female, Race.Viera)] = FunEquipSet.Group.FullSetWithoutHat(0581, 1),
};