Make states work.

This commit is contained in:
Ottermandias 2024-01-26 13:23:33 +01:00
parent 25ddbb1310
commit a4de13f228
27 changed files with 787 additions and 857 deletions

View file

@ -1,5 +1,4 @@
using Glamourer.Designs;
using Glamourer.Events;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Dalamud.Game.ClientState.Conditions;

View file

@ -0,0 +1,234 @@
using Dalamud.Plugin.Services;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Services;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.State;
public class InternalStateEditor(
CustomizeService customizations,
HumanModelList humans,
ItemManager items,
GPoseService gPose,
ICondition condition)
{
/// <summary> Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. </summary>
/// <remarks> We currently only allow changing things to humans, not humans to monsters. </remarks>
public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateSource source,
out uint oldModelId, uint key = 0)
{
oldModelId = state.ModelData.ModelId;
// TODO think about this.
if (modelId != 0)
return false;
if (!state.CanUnlock(key))
return false;
var oldIsHuman = state.ModelData.IsHuman;
state.ModelData.IsHuman = humans.IsHuman(modelId);
if (state.ModelData.IsHuman)
{
if (oldModelId == modelId)
return true;
state.ModelData.ModelId = modelId;
if (oldIsHuman)
return true;
if (!state.AllowsRedraw(condition))
return false;
// Fix up everything else to make sure the result is a valid human.
state.ModelData.Customize = CustomizeArray.Default;
state.ModelData.SetDefaultEquipment(items);
state.ModelData.SetHatVisible(true);
state.ModelData.SetWeaponVisible(true);
state.ModelData.SetVisor(false);
state.Sources[MetaIndex.ModelId] = source;
state.Sources[MetaIndex.HatState] = source;
state.Sources[MetaIndex.WeaponState] = source;
state.Sources[MetaIndex.VisorState] = source;
foreach (var slot in EquipSlotExtensions.FullSlots)
{
state.Sources[slot, true] = source;
state.Sources[slot, false] = source;
}
state.Sources[CustomizeIndex.Clan] = source;
state.Sources[CustomizeIndex.Gender] = source;
var set = customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender);
foreach (var index in Enum.GetValues<CustomizeIndex>().Where(set.IsAvailable))
state.Sources[index] = source;
}
else
{
if (!state.AllowsRedraw(condition))
return false;
state.ModelData.LoadNonHuman(modelId, customize, equipData);
state.Sources[MetaIndex.ModelId] = source;
}
return true;
}
/// <summary> Change a customization value. </summary>
public bool ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateSource source,
out CustomizeValue old, uint key = 0)
{
old = state.ModelData.Customize[idx];
if (!state.CanUnlock(key))
return false;
state.ModelData.Customize[idx] = value;
state.Sources[idx] = source;
return true;
}
/// <summary> Change an entire customization array according to functions. </summary>
public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich,
Func<CustomizeIndex, StateSource> source, out CustomizeArray old, out CustomizeFlag changed, uint key = 0)
{
old = state.ModelData.Customize;
changed = 0;
if (!state.CanUnlock(key))
return false;
(var customize, var applied, changed) = customizations.Combine(state.ModelData.Customize, customizeInput, applyWhich, true);
if (changed == 0)
return false;
state.ModelData.Customize = customize;
applied |= changed;
foreach (var type in Enum.GetValues<CustomizeIndex>())
{
if (applied.HasFlag(type.ToFlag()))
state.Sources[type] = source(type);
}
return true;
}
/// <summary> Change an entire customization array according to functions. </summary>
public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, Func<CustomizeIndex, bool> applyWhich,
Func<CustomizeIndex, StateSource> source, out CustomizeArray old, out CustomizeFlag changed, uint key = 0)
{
var apply = Enum.GetValues<CustomizeIndex>().Where(applyWhich).Aggregate((CustomizeFlag)0, (current, type) => current | type.ToFlag());
return ChangeHumanCustomize(state, customizeInput, apply, source, out old, out changed, key);
}
/// <summary> Change a single piece of equipment without stain. </summary>
public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateSource source, out EquipItem oldItem, uint key = 0)
{
oldItem = state.ModelData.Item(slot);
if (!state.CanUnlock(key))
return false;
// Can not change weapon type from expected type in state.
if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType
|| slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType)
{
if (!gPose.InGPose)
return false;
var old = oldItem;
gPose.AddActionOnLeave(() =>
{
if (old.Type == state.BaseData.Item(slot).Type)
ChangeItem(state, slot, old, state.Sources[slot, false], out _, key);
});
}
state.ModelData.SetItem(slot, item);
state.Sources[slot, false] = source;
return true;
}
/// <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)
{
oldItem = state.ModelData.Item(slot);
oldStain = state.ModelData.Stain(slot);
if (!state.CanUnlock(key))
return false;
// Can not change weapon type from expected type in state.
if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType
|| slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType)
{
if (!gPose.InGPose)
return false;
var old = oldItem;
var oldS = oldStain;
gPose.AddActionOnLeave(() =>
{
if (old.Type == state.BaseData.Item(slot).Type)
ChangeEquip(state, slot, old, oldS, state.Sources[slot, false], out _, out _, key);
});
}
state.ModelData.SetItem(slot, item);
state.ModelData.SetStain(slot, stain);
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)
{
oldStain = state.ModelData.Stain(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetStain(slot, stain);
state.Sources[slot, true] = source;
return true;
}
/// <summary> Change the crest of an equipment piece. </summary>
public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateSource source, out bool oldCrest, uint key = 0)
{
oldCrest = state.ModelData.Crest(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetCrest(slot, crest);
state.Sources[slot] = source;
return true;
}
/// <summary> Change the customize flags of a character. </summary>
public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateSource source,
out CustomizeParameterValue oldValue, uint key = 0)
{
oldValue = state.ModelData.Parameters[flag];
if (!state.CanUnlock(key))
return false;
state.ModelData.Parameters.Set(flag, value);
state.Sources[flag] = source;
return true;
}
public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue,
uint key = 0)
{
oldValue = state.ModelData.GetMeta(index);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetMeta(index, value);
state.Sources[index] = source;
return true;
}
}

View file

@ -0,0 +1,30 @@
using OtterGui.Services;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.State;
public sealed class JobChangeState : Dictionary<FullEquipType, (EquipItem, StateSource)>, IService
{
public ActorState? State { get; private set; }
public void Reset()
{
State = null;
Clear();
}
public bool HasState
=> State != null;
public ActorIdentifier Identifier
=> State?.Identifier ?? ActorIdentifier.Invalid;
public void Set(ActorState state, IEnumerable<(EquipItem, StateSource)> items)
{
foreach (var (item, source) in items.Where(p => p.Item1.Valid))
TryAdd(item.Type, (item, source));
State = state;
}
}

View file

@ -1,5 +1,4 @@
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
@ -277,6 +276,47 @@ public class StateApplier(
return data;
}
/// <summary> Apply the entire state of an actor to all relevant actors, either via immediate redraw or piecewise. </summary>
/// <param name="state"> The state to apply. </param>
/// <param name="redraw"> Whether a redraw should be forced. </param>
/// <param name="withLock"> Whether a temporary lock should be applied for the redraw. </param>
/// <returns> The actor data for the actors who got changed. </returns>
public ActorData ApplyAll(ActorState state, bool redraw, bool withLock)
{
var actors = ChangeMetaState(state, MetaIndex.Wetness, true);
if (redraw)
{
if (withLock)
state.TempLock();
ForceRedraw(actors);
}
else
{
ChangeCustomize(actors, state.ModelData.Customize);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.Sources[slot, false] is not StateSource.Ipc,
state.ModelData.IsHatVisible());
}
var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors;
ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand));
var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors;
ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand));
}
if (state.ModelData.IsHuman)
{
ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible());
ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible());
ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled());
ChangeCrests(actors, state.ModelData.CrestVisibility);
ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked);
}
return actors;
}
private ActorData GetData(ActorState state)
{
_objects.Update();

View file

@ -1,221 +1,334 @@
using Dalamud.Plugin.Services;
using Glamourer.Designs;
using Glamourer.Designs.Links;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.State;
public class StateEditor(CustomizeService customizations, HumanModelList humans, ItemManager items, GPoseService gPose, ICondition condition)
public class StateEditor(
InternalStateEditor editor,
StateApplier applier,
StateChanged stateChanged,
JobChangeState jobChange,
Configuration config,
ItemManager items) : IDesignEditor
{
/// <summary> Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. </summary>
/// <remarks> We currently only allow changing things to humans, not humans to monsters. </remarks>
public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateSource source,
out uint oldModelId, uint key = 0)
{
oldModelId = state.ModelData.ModelId;
protected readonly InternalStateEditor Editor = editor;
protected readonly StateApplier Applier = applier;
protected readonly StateChanged StateChanged = stateChanged;
protected readonly Configuration Config = config;
protected readonly ItemManager Items = items;
// TODO think about this.
if (modelId != 0)
return false;
if (!state.CanUnlock(key))
return false;
var oldIsHuman = state.ModelData.IsHuman;
state.ModelData.IsHuman = humans.IsHuman(modelId);
if (state.ModelData.IsHuman)
{
if (oldModelId == modelId)
return true;
state.ModelData.ModelId = modelId;
if (oldIsHuman)
return true;
if (!state.AllowsRedraw(condition))
return false;
// Fix up everything else to make sure the result is a valid human.
state.ModelData.Customize = CustomizeArray.Default;
state.ModelData.SetDefaultEquipment(items);
state.ModelData.SetHatVisible(true);
state.ModelData.SetWeaponVisible(true);
state.ModelData.SetVisor(false);
state.Sources[MetaIndex.ModelId] = source;
state.Sources[MetaIndex.HatState] = source;
state.Sources[MetaIndex.WeaponState] = source;
state.Sources[MetaIndex.VisorState] = source;
foreach (var slot in EquipSlotExtensions.FullSlots)
{
state.Sources[slot, true] = source;
state.Sources[slot, false] = source;
}
state.Sources[CustomizeIndex.Clan] = source;
state.Sources[CustomizeIndex.Gender] = source;
var set = customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender);
foreach (var index in Enum.GetValues<CustomizeIndex>().Where(set.IsAvailable))
state.Sources[index] = source;
}
else
{
if (!state.AllowsRedraw(condition))
return false;
state.ModelData.LoadNonHuman(modelId, customize, equipData);
state.Sources[MetaIndex.ModelId] = source;
}
return true;
}
/// <summary> Change a customization value. </summary>
public bool ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateSource source,
out CustomizeValue old, uint key = 0)
{
old = state.ModelData.Customize[idx];
if (!state.CanUnlock(key))
return false;
state.ModelData.Customize[idx] = value;
state.Sources[idx] = source;
return true;
}
/// <summary> Change an entire customization array according to flags. </summary>
public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich, StateSource source,
out CustomizeArray old, out CustomizeFlag changed, uint key = 0)
{
old = state.ModelData.Customize;
changed = 0;
if (!state.CanUnlock(key))
return false;
(var customize, var applied, changed) = customizations.Combine(state.ModelData.Customize, customizeInput, applyWhich, true);
if (changed == 0)
return false;
state.ModelData.Customize = customize;
applied |= changed;
foreach (var type in Enum.GetValues<CustomizeIndex>())
{
if (applied.HasFlag(type.ToFlag()))
state.Sources[type] = source;
}
return true;
}
/// <summary> Change a single piece of equipment without stain. </summary>
public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateSource source, out EquipItem oldItem, uint key = 0)
{
oldItem = state.ModelData.Item(slot);
if (!state.CanUnlock(key))
return false;
// Can not change weapon type from expected type in state.
if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType
|| slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType)
{
if (!gPose.InGPose)
return false;
var old = oldItem;
gPose.AddActionOnLeave(() =>
{
if (old.Type == state.BaseData.Item(slot).Type)
ChangeItem(state, slot, old, state.Sources[slot, false], out _, key);
});
}
state.ModelData.SetItem(slot, item);
state.Sources[slot, false] = source;
return true;
}
/// <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)
{
oldItem = state.ModelData.Item(slot);
oldStain = state.ModelData.Stain(slot);
if (!state.CanUnlock(key))
return false;
// Can not change weapon type from expected type in state.
if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType
|| slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType)
{
if (!gPose.InGPose)
return false;
var old = oldItem;
var oldS = oldStain;
gPose.AddActionOnLeave(() =>
{
if (old.Type == state.BaseData.Item(slot).Type)
ChangeEquip(state, slot, old, oldS, state.Sources[slot, false], out _, out _, key);
});
}
state.ModelData.SetItem(slot, item);
state.ModelData.SetStain(slot, stain);
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)
{
oldStain = state.ModelData.Stain(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetStain(slot, stain);
state.Sources[slot, true] = source;
return true;
}
/// <summary> Change the crest of an equipment piece. </summary>
public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateSource source, out bool oldCrest, uint key = 0)
{
oldCrest = state.ModelData.Crest(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetCrest(slot, crest);
state.Sources[slot] = source;
return true;
}
/// <summary> Change the customize flags of a character. </summary>
public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateSource source,
out CustomizeParameterValue oldValue, uint key = 0)
{
oldValue = state.ModelData.Parameters[flag];
if (!state.CanUnlock(key))
return false;
state.ModelData.Parameters.Set(flag, value);
state.Sources[flag] = source;
return true;
}
public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue,
/// <summary> Turn an actor to. </summary>
public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source,
uint key = 0)
{
oldValue = state.ModelData.GetMeta(index);
if (!state.CanUnlock(key))
return false;
if (!Editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key))
return;
state.ModelData.SetMeta(index, value);
state.Sources[index] = source;
return true;
var actors = Applier.ForceRedraw(state, source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId));
}
/// <inheritdoc/>
public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings settings)
{
if (data is not ActorState state)
return;
if (!Editor.ChangeCustomize(state, idx, value, settings.Source, out var old, settings.Key))
return;
var actors = Applier.ChangeCustomize(state, settings.Source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Customize, settings.Source, state, actors, (old, value, idx));
}
/// <inheritdoc/>
public void ChangeEntireCustomize(object data, in CustomizeArray customizeInput, CustomizeFlag apply, ApplySettings settings)
{
if (data is not ActorState state)
return;
if (!Editor.ChangeHumanCustomize(state, customizeInput, apply, _ => settings.Source, out var old, out var applied, settings.Key))
return;
var actors = Applier.ChangeCustomize(state, settings.Source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.EntireCustomize, settings.Source, state, actors, (old, applied));
}
/// <inheritdoc/>
public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default)
{
if (data is not ActorState state)
return;
if (!Editor.ChangeItem(state, slot, item, settings.Source, out var old, settings.Key))
return;
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
var actors = type is StateChanged.Type.Equip
? Applier.ChangeArmor(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc)
: Applier.ChangeWeapon(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc,
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
if (slot is EquipSlot.MainHand)
ApplyMainhandPeriphery(state, item, settings);
Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(type, settings.Source, state, actors, (old, item, slot));
}
/// <inheritdoc/>
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings)
{
switch (item.HasValue, stain.HasValue)
{
case (false, false): return;
case (true, false):
ChangeItem(data, slot, item!.Value, settings);
return;
case (false, true):
ChangeStain(data, slot, stain!.Value, settings);
return;
}
if (data is not ActorState state)
return;
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))
return;
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
var actors = type is StateChanged.Type.Equip
? Applier.ChangeArmor(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc)
: Applier.ChangeWeapon(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc,
item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
if (slot is EquipSlot.MainHand)
ApplyMainhandPeriphery(state, item, 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(StateChanged.Type.Stain, settings.Source, state, actors, (oldStain, stain!.Value, slot));
}
/// <inheritdoc/>
public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings)
{
if (data is not ActorState state)
return;
if (!Editor.ChangeStain(state, slot, stain, settings.Source, out var old, settings.Key))
return;
var actors = Applier.ChangeStain(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc);
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(StateChanged.Type.Stain, settings.Source, state, actors, (old, stain, slot));
}
/// <inheritdoc/>
public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings)
{
if (data is not ActorState state)
return;
if (!Editor.ChangeCrest(state, slot, crest, settings.Source, out var old, settings.Key))
return;
var actors = Applier.ChangeCrests(state, settings.Source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Crest, settings.Source, state, actors, (old, crest, slot));
}
/// <inheritdoc/>
public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings settings)
{
if (data is not ActorState state)
return;
// Also apply main color to highlights when highlights is off.
if (!state.ModelData.Customize.Highlights && flag is CustomizeParameterFlag.HairDiffuse)
ChangeCustomizeParameter(state, CustomizeParameterFlag.HairHighlight, value, settings);
if (!Editor.ChangeParameter(state, flag, value, settings.Source, out var old, settings.Key))
return;
var @new = state.ModelData.Parameters[flag];
var actors = Applier.ChangeParameters(state, flag, settings.Source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Parameter, settings.Source, state, actors, (old, @new, flag));
}
/// <inheritdoc/>
public void ChangeMetaState(object data, MetaIndex index, bool value, ApplySettings settings)
{
if (data is not ActorState state)
return;
if (!Editor.ChangeMetaState(state, index, value, settings.Source, out var old, settings.Key))
return;
var actors = Applier.ChangeMetaState(state, index, settings.Source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Other, settings.Source, state, actors, (old, value, MetaIndex.HatState));
}
/// <inheritdoc/>
public void ApplyDesign(object data, MergedDesign mergedDesign, ApplySettings settings)
{
if (data is not ActorState state)
return;
if (!Editor.ChangeModelId(state, mergedDesign.Design.DesignData.ModelId, mergedDesign.Design.DesignData.Customize,
mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key))
return;
var requiresRedraw = oldModelId != mergedDesign.Design.DesignData.ModelId || !mergedDesign.Design.DesignData.IsHuman;
if (state.ModelData.IsHuman)
{
foreach (var slot in CrestExtensions.AllRelevantSet.Where(mergedDesign.Design.DoApplyCrest))
{
if (!settings.RespectManual || state.Sources[slot] is not StateSource.Manual)
Editor.ChangeCrest(state, slot, mergedDesign.Design.DesignData.Crest(slot), Source(slot),
out _, settings.Key);
}
var customizeFlags = mergedDesign.Design.ApplyCustomizeRaw;
if (mergedDesign.Design.DoApplyCustomize(CustomizeIndex.Clan))
customizeFlags |= CustomizeFlag.Race;
Func<CustomizeIndex, bool> applyWhich = settings.RespectManual
? i => customizeFlags.HasFlag(i.ToFlag()) && state.Sources[i] is not StateSource.Manual
: i => customizeFlags.HasFlag(i.ToFlag());
if (Editor.ChangeHumanCustomize(state, mergedDesign.Design.DesignData.Customize, applyWhich, i => Source(i), out _, out var changed,
settings.Key))
requiresRedraw |= changed.RequiresRedraw();
foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate())
{
if (settings.RespectManual && state.Sources[parameter] is StateSource.Manual or StateSource.Pending)
continue;
var source = Source(parameter);
if (source is StateSource.Manual)
source = StateSource.Pending;
Editor.ChangeParameter(state, parameter, mergedDesign.Design.DesignData.Parameters[parameter], source, out _, settings.Key);
}
// Do not apply highlights from a design if highlights is unchecked.
if (!state.ModelData.Customize.Highlights)
Editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight,
state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse],
state.Sources[CustomizeParameterFlag.HairDiffuse], out _, settings.Key);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
if (mergedDesign.Design.DoApplyEquip(slot))
if (!settings.RespectManual || state.Sources[slot, false] is not StateSource.Manual)
Editor.ChangeItem(state, slot, mergedDesign.Design.DesignData.Item(slot),
Source(slot.ToState()), out _, settings.Key);
if (mergedDesign.Design.DoApplyStain(slot))
if (!settings.RespectManual || state.Sources[slot, true] is not StateSource.Manual)
Editor.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot),
Source(slot.ToState(true)), out _, settings.Key);
}
foreach (var weaponSlot in EquipSlotExtensions.WeaponSlots)
{
if (mergedDesign.Design.DoApplyStain(weaponSlot))
if (!settings.RespectManual || state.Sources[weaponSlot, true] is not StateSource.Manual)
Editor.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot),
Source(weaponSlot.ToState(true)), out _, settings.Key);
if (!mergedDesign.Design.DoApplyEquip(weaponSlot))
continue;
if (settings.RespectManual && state.Sources[weaponSlot, false] is StateSource.Manual)
continue;
var currentType = state.ModelData.Item(weaponSlot).Type;
if (!settings.FromJobChange && mergedDesign.Weapons.TryGetValue(currentType, out var weapon))
{
var source = settings.UseSingleSource ? settings.Source :
weapon.Item2 is StateSource.Game ? StateSource.Game : weapon.Item2;
Editor.ChangeItem(state, weaponSlot, weapon.Item1, source, out _,
settings.Key);
}
}
if (settings.FromJobChange)
jobChange.Set(state, mergedDesign.Weapons.Values.Select(m =>
(m.Item1, settings.UseSingleSource ? settings.Source :
m.Item2 is StateSource.Game ? StateSource.Game : m.Item2)));
foreach (var meta in MetaExtensions.AllRelevant)
{
if (!settings.RespectManual || state.Sources[meta] is not StateSource.Manual)
Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key);
}
}
var actors = settings.Source is StateSource.Manual or StateSource.Ipc
? Applier.ApplyAll(state, requiresRedraw, false)
: ActorData.Invalid;
Glamourer.Log.Verbose(
$"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Design, state.Sources[MetaIndex.Wetness], state, actors, mergedDesign.Design);
return;
StateSource Source(StateIndex index)
{
if (settings.UseSingleSource)
return settings.Source;
var source = mergedDesign.Sources[index];
return source is StateSource.Game ? StateSource.Game : settings.Source;
}
}
public void ApplyDesign(object data, DesignBase design, ApplySettings settings)
=> ApplyDesign(data, new MergedDesign(design), settings with
{
FromJobChange = false,
RespectManual = false,
UseSingleSource = true,
});
/// <summary> Apply offhand item and potentially gauntlets if configured. </summary>
private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, ApplySettings settings)
{
if (!Config.ChangeEntireItem || settings.Source is not StateSource.Manual)
return;
var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand);
var offhand = newMainhand != null ? Items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand);
if (offhand.Valid)
ChangeEquip(state, EquipSlot.OffHand, offhand, state.ModelData.Stain(EquipSlot.OffHand), 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),
state.ModelData.Stain(EquipSlot.Hands), settings);
}
}

View file

@ -139,7 +139,7 @@ public class StateListener : IDisposable
ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender);
}
private unsafe void OnCustomizeChange(Model model, ref CustomizeArray customize)
private void OnCustomizeChange(Model model, ref CustomizeArray customize)
{
if (!model.IsHuman)
return;
@ -164,7 +164,7 @@ public class StateListener : IDisposable
var model = state.ModelData.Customize;
if (customize.Gender != model.Gender || customize.Clan != model.Clan)
{
_manager.ChangeCustomize(state, in customize, CustomizeFlagExtensions.AllRelevant, StateSource.Game);
_manager.ChangeEntireCustomize(state, in customize, CustomizeFlagExtensions.All, ApplySettings.Game);
return;
}
@ -178,7 +178,7 @@ public class StateListener : IDisposable
if (newValue != oldValue)
{
if (set.Validate(index, newValue, out _, model.Face))
_manager.ChangeCustomize(state, index, newValue, StateSource.Game);
_manager.ChangeCustomize(state, index, newValue, ApplySettings.Game);
else
customize[index] = oldValue;
}
@ -243,8 +243,8 @@ public class StateListener : IDisposable
var changed = changedItem.Weapon(stain);
if (current.Value == changed.Value && state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc)
{
_manager.ChangeItem(state, slot, currentItem, StateSource.Game);
_manager.ChangeStain(state, slot, current.Stain, StateSource.Game);
_manager.ChangeItem(state, slot, currentItem, ApplySettings.Game);
_manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game);
switch (slot)
{
case EquipSlot.MainHand:
@ -287,12 +287,12 @@ public class StateListener : IDisposable
case UpdateState.Transformed: break;
case UpdateState.Change:
if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc)
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateSource.Game);
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game);
else
apply = true;
if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc)
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateSource.Game);
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
else
apply = true;
break;
@ -386,12 +386,12 @@ public class StateListener : IDisposable
case UpdateState.Change:
var apply = false;
if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc)
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateSource.Game);
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game);
else
apply = true;
if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc)
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateSource.Game);
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
else
apply = true;
@ -420,7 +420,7 @@ public class StateListener : IDisposable
{
case UpdateState.Change:
if (state.Sources[slot] is not StateSource.Fixed and not StateSource.Ipc)
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateSource.Game);
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), ApplySettings.Game);
else
value = state.ModelData.Crest(slot);
break;
@ -568,7 +568,7 @@ public class StateListener : IDisposable
if (state.Sources[MetaIndex.VisorState] is StateSource.Fixed or StateSource.Ipc)
value = state.ModelData.IsVisorToggled();
else
_manager.ChangeMeta(state, MetaIndex.VisorState, value, StateSource.Game);
_manager.ChangeMetaState(state, MetaIndex.VisorState, value, ApplySettings.Game);
}
else
{
@ -601,7 +601,7 @@ public class StateListener : IDisposable
if (state.Sources[MetaIndex.HatState] is StateSource.Fixed or StateSource.Ipc)
value = state.ModelData.IsHatVisible();
else
_manager.ChangeMeta(state, MetaIndex.HatState, value, StateSource.Game);
_manager.ChangeMetaState(state, MetaIndex.HatState, value, ApplySettings.Game);
}
else
{
@ -634,7 +634,7 @@ public class StateListener : IDisposable
if (state.Sources[MetaIndex.WeaponState] is StateSource.Fixed or StateSource.Ipc)
value = state.ModelData.IsWeaponVisible();
else
_manager.ChangeMeta(state, MetaIndex.WeaponState, value, StateSource.Game);
_manager.ChangeMetaState(state, MetaIndex.WeaponState, value, ApplySettings.Game);
}
else
{
@ -724,7 +724,7 @@ public class StateListener : IDisposable
_customizeState = null;
}
private unsafe void ApplyParameters(ActorState state, Model model)
private void ApplyParameters(ActorState state, Model model)
{
if (!model.IsHuman)
return;
@ -737,11 +737,11 @@ public class StateListener : IDisposable
{
case StateSource.Game:
if (state.BaseData.Parameters.Set(flag, newValue))
_manager.ChangeCustomizeParameter(state, flag, newValue, StateSource.Game);
_manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game);
break;
case StateSource.Manual:
if (state.BaseData.Parameters.Set(flag, newValue))
_manager.ChangeCustomizeParameter(state, flag, newValue, StateSource.Game);
_manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game);
else if (_config.UseAdvancedParameters)
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
break;

View file

@ -12,17 +12,17 @@ using Penumbra.GameData.Structs;
namespace Glamourer.State;
public class StateManager(
public sealed class StateManager(
ActorManager _actors,
ItemManager _items,
StateChanged _event,
StateApplier _applier,
StateEditor _editor,
ItemManager items,
StateChanged @event,
StateApplier applier,
InternalStateEditor editor,
HumanModelList _humans,
ICondition _condition,
IClientState _clientState,
Configuration _config)
: IReadOnlyDictionary<ActorIdentifier, ActorState>
Configuration config,
JobChangeState jobChange)
: StateEditor(editor, applier, @event, jobChange, config, items), IReadOnlyDictionary<ActorIdentifier, ActorState>
{
private readonly Dictionary<ActorIdentifier, ActorState> _states = [];
@ -93,7 +93,7 @@ public class StateManager(
// If the given actor is not a character, just return a default character.
if (!actor.IsCharacter)
{
ret.SetDefaultEquipment(_items);
ret.SetDefaultEquipment(Items);
return ret;
}
@ -127,7 +127,7 @@ public class StateManager(
// We can not use the head slot data from the draw object if the hat is hidden.
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.SetStain(EquipSlot.Head, head.Stain);
@ -135,7 +135,7 @@ public class StateManager(
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
{
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.SetStain(slot, armor.Stain);
}
@ -157,7 +157,7 @@ public class StateManager(
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
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.SetStain(slot, armor.Stain);
}
@ -172,8 +172,8 @@ public class StateManager(
}
// Set the weapons regardless of source.
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 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.SetItem(EquipSlot.OffHand, offItem);
@ -197,7 +197,7 @@ public class StateManager(
if (mainhand.Skeleton.Id is < 1601 or >= 1651)
return;
var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id);
var gauntlets = Items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id);
offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50);
offhand.Variant = mainhand.Variant;
offhand.Weapon = mainhand.Weapon;
@ -205,251 +205,10 @@ public class StateManager(
ret.SetStain(EquipSlot.Hands, mainhand.Stain);
}
#region Change Values
/// <summary> Turn an actor human. </summary>
public void TurnHuman(ActorState state, StateSource source, uint key = 0)
=> ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key);
/// <summary> Turn an actor to. </summary>
public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source,
uint key = 0)
{
if (!_editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key))
return;
var actors = _applier.ForceRedraw(state, source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId));
}
/// <summary> Change a customization value. </summary>
public void ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateSource source, uint key = 0)
{
if (!_editor.ChangeCustomize(state, idx, value, source, out var old, key))
return;
var actors = _applier.ChangeCustomize(state, source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Customize, source, state, actors, (old, value, idx));
}
/// <summary> Change an entire customization array according to flags. </summary>
public void ChangeCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag apply, StateSource source,
uint key = 0)
{
if (!_editor.ChangeHumanCustomize(state, customizeInput, apply, source, out var old, out var applied, key))
return;
var actors = _applier.ChangeCustomize(state, source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.EntireCustomize, source, state, actors, (old, applied));
}
/// <summary> Change a single piece of equipment without stain. </summary>
/// <remarks> Do not use this in the same frame as ChangeStain, use <see cref="ChangeEquip(ActorState,EquipSlot,EquipItem,StainId,StateSource,uint)"/> instead. </remarks>
public void ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateSource source, uint key = 0)
{
if (!_editor.ChangeItem(state, slot, item, source, out var old, key))
return;
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
var actors = type is StateChanged.Type.Equip
? _applier.ChangeArmor(state, slot, source is StateSource.Manual or StateSource.Ipc)
: _applier.ChangeWeapon(state, slot, source is StateSource.Manual or StateSource.Ipc,
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
if (slot is EquipSlot.MainHand)
ApplyMainhandPeriphery(state, item, source, key);
Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(type, source, state, actors, (old, item, slot));
}
/// <summary> Change a single piece of equipment including stain. </summary>
public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateSource source, uint key = 0)
{
if (!_editor.ChangeEquip(state, slot, item, stain, source, out var old, out var oldStain, key))
return;
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
var actors = type is StateChanged.Type.Equip
? _applier.ChangeArmor(state, slot, source is StateSource.Manual or StateSource.Ipc)
: _applier.ChangeWeapon(state, slot, source is StateSource.Manual or StateSource.Ipc,
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
if (slot is EquipSlot.MainHand)
ApplyMainhandPeriphery(state, item, source, key);
Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}) and its stain from {oldStain.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(type, source, state, actors, (old, item, slot));
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (oldStain, stain, slot));
}
/// <summary> Change only the stain of an equipment piece. </summary>
/// <remarks> Do not use this in the same frame as ChangeEquip, use <see cref="ChangeEquip(ActorState,EquipSlot,EquipItem,StainId,StateSource,uint)"/> instead. </remarks>
public void ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateSource source, uint key = 0)
{
if (!_editor.ChangeStain(state, slot, stain, source, out var old, key))
return;
var actors = _applier.ChangeStain(state, slot, source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot));
}
/// <summary> Change the crest of an equipment piece. </summary>
public void ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateSource source, uint key = 0)
{
if (!_editor.ChangeCrest(state, slot, crest, source, out var old, key))
return;
var actors = _applier.ChangeCrests(state, source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Crest, source, state, actors, (old, crest, slot));
}
/// <summary> Change the crest of an equipment piece. </summary>
public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value,
StateSource source, uint key = 0)
{
// Also apply main color to highlights when highlights is off.
if (!state.ModelData.Customize.Highlights && flag is CustomizeParameterFlag.HairDiffuse)
ChangeCustomizeParameter(state, CustomizeParameterFlag.HairHighlight, value, source, key);
if (!_editor.ChangeParameter(state, flag, value, source, out var old, key))
return;
var @new = state.ModelData.Parameters[flag];
var actors = _applier.ChangeParameters(state, flag, source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Parameter, source, state, actors, (old, @new, flag));
}
/// <summary> Change meta state. </summary>
public void ChangeMeta(ActorState state, MetaIndex meta, bool value, StateSource source, uint key = 0)
{
if (!_editor.ChangeMetaState(state, meta, value, source, out var old, key))
return;
var actors = _applier.ChangeMetaState(state, meta, source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.HatState));
}
#endregion
public void ApplyDesign(DesignBase design, ActorState state, StateSource source, uint key = 0)
{
if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(),
source,
out var oldModelId, key))
return;
var redraw = oldModelId != design.DesignData.ModelId || !design.DesignData.IsHuman;
if (design.DoApplyMeta(MetaIndex.Wetness))
_editor.ChangeMetaState(state, MetaIndex.Wetness, design.DesignData.IsWet(), source, out _, key);
if (state.ModelData.IsHuman)
{
if (design.DoApplyMeta(MetaIndex.HatState))
_editor.ChangeMetaState(state, MetaIndex.HatState, design.DesignData.IsHatVisible(), source, out _, key);
if (design.DoApplyMeta(MetaIndex.WeaponState))
_editor.ChangeMetaState(state, MetaIndex.WeaponState, design.DesignData.IsWeaponVisible(), source, out _, key);
if (design.DoApplyMeta(MetaIndex.VisorState))
_editor.ChangeMetaState(state, MetaIndex.VisorState, design.DesignData.IsVisorToggled(), source, out _, key);
var flags = state.AllowsRedraw(_condition)
? design.ApplyCustomize
: design.ApplyCustomize & ~CustomizeFlagExtensions.RedrawRequired;
_editor.ChangeHumanCustomize(state, design.DesignData.Customize, flags, source, out _, out var applied, key);
redraw |= applied.RequiresRedraw();
foreach (var slot in EquipSlotExtensions.FullSlots)
HandleEquip(slot, design.DoApplyEquip(slot), design.DoApplyStain(slot));
foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest))
_editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key);
var paramSource = source is StateSource.Manual
? StateSource.Pending
: source;
foreach (var flag in CustomizeParameterExtensions.AllFlags.Where(design.DoApplyParameter))
_editor.ChangeParameter(state, flag, design.DesignData.Parameters[flag], paramSource, out _, key);
// Do not apply highlights from a design if highlights is unchecked.
if (!state.ModelData.Customize.Highlights)
_editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight,
state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse],
state.Sources[CustomizeParameterFlag.HairDiffuse], out _, key);
}
var actors = ApplyAll(state, redraw, false);
Glamourer.Log.Verbose(
$"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Design, state.Sources[MetaIndex.Wetness], state, actors, design);
return;
void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain)
{
var unused = (applyPiece, applyStain) switch
{
(false, false) => false,
(true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key),
(false, true) => _editor.ChangeStain(state, slot, design.DesignData.Stain(slot), source, out _, key),
(true, true) => _editor.ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), source, out _,
out _, key),
};
}
}
private ActorData ApplyAll(ActorState state, bool redraw, bool withLock)
{
var actors = _applier.ChangeMetaState(state, MetaIndex.Wetness, true);
if (redraw)
{
if (withLock)
state.TempLock();
_applier.ForceRedraw(actors);
}
else
{
_applier.ChangeCustomize(actors, state.ModelData.Customize);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
_applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.Sources[slot, false] is not StateSource.Ipc,
state.ModelData.IsHatVisible());
}
var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors;
_applier.ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand));
var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors;
_applier.ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand));
}
if (state.ModelData.IsHuman)
{
_applier.ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible());
_applier.ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible());
_applier.ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled());
_applier.ChangeCrests(actors, state.ModelData.CrestVisibility);
_applier.ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked);
}
return actors;
}
public void ResetState(ActorState state, StateSource source, uint key = 0)
{
if (!state.Unlock(key))
@ -481,11 +240,11 @@ public class StateManager(
var actors = ActorData.Invalid;
if (source is StateSource.Manual or StateSource.Ipc)
actors = ApplyAll(state, redraw, true);
actors = Applier.ApplyAll(state, redraw, true);
Glamourer.Log.Verbose(
$"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Reset, source, state, actors, null);
StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null);
}
public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0)
@ -500,10 +259,10 @@ public class StateManager(
var actors = ActorData.Invalid;
if (source is StateSource.Manual or StateSource.Ipc)
actors = _applier.ChangeParameters(state, CustomizeParameterExtensions.All, true);
actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true);
Glamourer.Log.Verbose(
$"Reset advanced customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Reset, source, state, actors, null);
StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null);
}
public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0)
@ -583,26 +342,11 @@ public class StateManager(
if (!GetOrCreate(actor, out var state))
return;
ApplyAll(state, !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(),
Applier.ApplyAll(state,
!actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(),
false);
}
public void DeleteState(ActorIdentifier identifier)
=> _states.Remove(identifier);
/// <summary> Apply offhand item and potentially gauntlets if configured. </summary>
private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, StateSource source, uint key = 0)
{
if (!_config.ChangeEntireItem || source is not StateSource.Manual)
return;
var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand);
var offhand = newMainhand != null ? _items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand);
if (offhand.Valid)
ChangeEquip(state, EquipSlot.OffHand, offhand, state.ModelData.Stain(EquipSlot.OffHand), source, key);
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),
state.ModelData.Stain(EquipSlot.Hands), source, key);
}
}