Update OtterGui

This commit is contained in:
Ottermandias 2024-01-06 23:56:19 +01:00
parent 6130bae81d
commit 6ecf06a671
23 changed files with 142 additions and 254 deletions

View file

@ -11,7 +11,6 @@ using Glamourer.Interop.Structs;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Unlocks;
using OtterGui.Classes;
using Penumbra.GameData.Actors;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
@ -80,7 +79,7 @@ public class AutoDesignApplier : IDisposable
_jobs.JobChanged -= OnJobChange;
}
private void OnWeaponLoading(Actor actor, EquipSlot slot, Ref<CharacterWeapon> weapon)
private void OnWeaponLoading(Actor actor, EquipSlot slot, ref CharacterWeapon weapon)
{
if (_jobChangeState == null || !_config.EnableAutoDesigns)
return;
@ -89,27 +88,33 @@ public class AutoDesignApplier : IDisposable
if (id == _jobChangeState.Identifier)
{
var current = _jobChangeState.BaseData.Item(slot);
if (slot is EquipSlot.MainHand)
switch (slot)
{
case EquipSlot.MainHand:
{
if (_jobChangeMainhand.TryGetValue(current.Type, out var data))
{
Glamourer.Log.Verbose(
$"Changing Mainhand from {_jobChangeState.ModelData.Weapon(EquipSlot.MainHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}.");
_state.ChangeItem(_jobChangeState, EquipSlot.MainHand, data.Item1, data.Item2);
weapon.Value = _jobChangeState.ModelData.Weapon(EquipSlot.MainHand);
weapon = _jobChangeState.ModelData.Weapon(EquipSlot.MainHand);
}
break;
}
else if (slot is EquipSlot.OffHand && current.Type == _jobChangeState.BaseData.MainhandType.Offhand())
case EquipSlot.OffHand when current.Type == _jobChangeState.BaseData.MainhandType.Offhand():
{
if (_jobChangeOffhand.TryGetValue(current.Type, out var data))
{
Glamourer.Log.Verbose(
$"Changing Offhand from {_jobChangeState.ModelData.Weapon(EquipSlot.OffHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}.");
_state.ChangeItem(_jobChangeState, EquipSlot.OffHand, data.Item1, data.Item2);
weapon.Value = _jobChangeState.ModelData.Weapon(EquipSlot.OffHand);
weapon = _jobChangeState.ModelData.Weapon(EquipSlot.OffHand);
}
ResetJobChange();
break;
}
}
}
else
@ -123,20 +128,31 @@ public class AutoDesignApplier : IDisposable
if (!_config.EnableAutoDesigns || set == null)
return;
void RemoveOld(ActorIdentifier[]? identifiers)
switch (type)
{
if (identifiers == null)
return;
case AutomationChanged.Type.ToggleSet when !set.Enabled:
case AutomationChanged.Type.DeletedDesign when set.Enabled:
// The automation set was disabled or deleted, no other for those identifiers can be enabled, remove existing Fixed Locks.
RemoveOld(set.Identifiers);
break;
case AutomationChanged.Type.ChangeIdentifier when set.Enabled:
// Remove fixed state from the old identifiers assigned and the old enabled set, if any.
var (oldIds, _, oldSet) = ((ActorIdentifier[], ActorIdentifier, AutoDesignSet?))bonusData!;
RemoveOld(oldIds);
ApplyNew(set); // Does not need to disable oldSet because same identifiers.
break;
case AutomationChanged.Type.ToggleSet: // Does not need to disable old states because same identifiers.
case AutomationChanged.Type.ChangedBase:
case AutomationChanged.Type.AddedDesign:
case AutomationChanged.Type.MovedDesign:
case AutomationChanged.Type.ChangedDesign:
case AutomationChanged.Type.ChangedConditions:
case AutomationChanged.Type.ChangedType:
ApplyNew(set);
break;
}
foreach (var id in identifiers)
{
if (id.Type is IdentifierType.Player && id.HomeWorld == WorldId.AnyWorld)
foreach (var state in _state.Where(kvp => kvp.Key.PlayerName == id.PlayerName).Select(kvp => kvp.Value))
state.RemoveFixedDesignSources();
else if (_state.TryGetValue(id, out var state))
state.RemoveFixedDesignSources();
}
}
return;
void ApplyNew(AutoDesignSet? newSet)
{
@ -174,28 +190,19 @@ public class AutoDesignApplier : IDisposable
}
}
switch (type)
void RemoveOld(ActorIdentifier[]? identifiers)
{
case AutomationChanged.Type.ToggleSet when !set.Enabled:
case AutomationChanged.Type.DeletedDesign when set.Enabled:
// The automation set was disabled or deleted, no other for those identifiers can be enabled, remove existing Fixed Locks.
RemoveOld(set.Identifiers);
break;
case AutomationChanged.Type.ChangeIdentifier when set.Enabled:
// Remove fixed state from the old identifiers assigned and the old enabled set, if any.
var (oldIds, _, oldSet) = ((ActorIdentifier[], ActorIdentifier, AutoDesignSet?))bonusData!;
RemoveOld(oldIds);
ApplyNew(set); // Does not need to disable oldSet because same identifiers.
break;
case AutomationChanged.Type.ToggleSet: // Does not need to disable old states because same identifiers.
case AutomationChanged.Type.ChangedBase:
case AutomationChanged.Type.AddedDesign:
case AutomationChanged.Type.MovedDesign:
case AutomationChanged.Type.ChangedDesign:
case AutomationChanged.Type.ChangedConditions:
case AutomationChanged.Type.ChangedType:
ApplyNew(set);
break;
if (identifiers == null)
return;
foreach (var id in identifiers)
{
if (id.Type is IdentifierType.Player && id.HomeWorld == WorldId.AnyWorld)
foreach (var state in _state.Where(kvp => kvp.Key.PlayerName == id.PlayerName).Select(kvp => kvp.Value))
state.RemoveFixedDesignSources();
else if (_state.TryGetValue(id, out var state))
state.RemoveFixedDesignSources();
}
}
}

View file

@ -99,7 +99,7 @@ public class DesignManager
Glamourer.Log.Information(
$"Loaded {_designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}");
_event.Invoke(DesignChanged.Type.ReloadedAll, null!);
_event.Invoke(DesignChanged.Type.ReloadedAll, null!, null);
}
/// <summary> Whether an Undo for the given design is possible. </summary>
@ -176,7 +176,7 @@ public class DesignManager
--d.Index;
_designs.RemoveAt(design.Index);
_saveService.ImmediateDelete(design);
_event.Invoke(DesignChanged.Type.Deleted, design);
_event.Invoke(DesignChanged.Type.Deleted, design, null);
}
/// <summary> Rename a design. </summary>
@ -723,7 +723,7 @@ public class DesignManager
if (!message.IsNullOrEmpty())
Glamourer.Log.Debug(message);
_saveService.ImmediateSave(design);
_event.Invoke(DesignChanged.Type.Created, design);
_event.Invoke(DesignChanged.Type.Created, design, null);
return true;
}

View file

@ -1,5 +1,4 @@
using System;
using Glamourer.Automation;
using Glamourer.Automation;
using OtterGui.Classes;
namespace Glamourer.Events;
@ -12,8 +11,8 @@ namespace Glamourer.Events;
/// <item>Parameter is additional data depending on the type of change. </item>
/// </list>
/// </summary>
public sealed class AutomationChanged : EventWrapper<Action<AutomationChanged.Type, AutoDesignSet?, object?>,
AutomationChanged.Priority>
public sealed class AutomationChanged()
: EventWrapper<AutomationChanged.Type, AutoDesignSet?, object?, AutomationChanged.Priority>(nameof(AutomationChanged))
{
public enum Type
{
@ -65,11 +64,4 @@ public sealed class AutomationChanged : EventWrapper<Action<AutomationChanged.Ty
/// <seealso cref="AutoDesignApplier.OnAutomationChange"/>
AutoDesignApplier,
}
public AutomationChanged()
: base(nameof(AutomationChanged))
{ }
public void Invoke(Type type, AutoDesignSet? set, object? data)
=> Invoke(this, type, set, data);
}

View file

@ -1,4 +1,3 @@
using System;
using Glamourer.Designs;
using Glamourer.Gui;
using OtterGui.Classes;
@ -13,7 +12,8 @@ namespace Glamourer.Events;
/// <item>Parameter is any additional data depending on the type of change. </item>
/// </list>
/// </summary>
public sealed class DesignChanged : EventWrapper<Action<DesignChanged.Type, Design, object?>, DesignChanged.Priority>
public sealed class DesignChanged()
: EventWrapper<DesignChanged.Type, Design, object?, DesignChanged.Priority>(nameof(DesignChanged))
{
public enum Type
{
@ -98,11 +98,4 @@ public sealed class DesignChanged : EventWrapper<Action<DesignChanged.Type, Desi
/// <seealso cref="RevertDesignCombo.OnDesignChange"/>
DesignCombo = -2,
}
public DesignChanged()
: base(nameof(DesignChanged))
{ }
public void Invoke(Type type, Design design, object? data = null)
=> Invoke(this, type, design, data);
}

View file

@ -1,4 +1,3 @@
using System;
using OtterGui.Classes;
namespace Glamourer.Events;
@ -13,18 +12,12 @@ namespace Glamourer.Events;
/// <item>Parameter is the job id of the associated job. </item>
/// </list>
/// </summary>
public sealed class EquippedGearset : EventWrapper<Action<string, int, int, byte, byte>, EquippedGearset.Priority>
public sealed class EquippedGearset()
: EventWrapper<string, int, int, byte, byte, EquippedGearset.Priority>(nameof(EquippedGearset))
{
public enum Priority
{
/// <seealso cref="Automation.AutoDesignApplier.OnEquippedGearset"/>
AutoDesignApplier = 0,
}
public EquippedGearset()
: base(nameof(EquippedGearset))
{ }
public void Invoke(string name, int id, int lastId, byte glamour, byte jobId)
=> Invoke(this, name, id, lastId, glamour, jobId);
}

View file

@ -5,7 +5,7 @@ using OtterGui.Classes;
namespace Glamourer.Events;
public sealed class GPoseService : EventWrapper<Action<bool>, GPoseService.Priority>
public sealed class GPoseService : EventWrapper<bool, GPoseService.Priority>
{
private readonly IFramework _framework;
private readonly IClientState _state;
@ -56,7 +56,7 @@ public sealed class GPoseService : EventWrapper<Action<bool>, GPoseService.Prior
return;
InGPose = inGPose;
Invoke(this, InGPose);
Invoke(InGPose);
var actions = InGPose ? _onEnter : _onLeave;
foreach (var action in actions)
{

View file

@ -1,4 +1,3 @@
using System;
using Glamourer.Interop.Structs;
using OtterGui.Classes;
@ -11,22 +10,12 @@ namespace Glamourer.Events;
/// <item>Parameter is the new state. </item>
/// </list>
/// </summary>
public sealed class HeadGearVisibilityChanged : EventWrapper<Action<Actor, Ref<bool>>, HeadGearVisibilityChanged.Priority>
public sealed class HeadGearVisibilityChanged()
: EventWrapperRef2<Actor, bool, HeadGearVisibilityChanged.Priority>(nameof(HeadGearVisibilityChanged))
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnHeadGearVisibilityChange"/>
StateListener = 0,
}
public HeadGearVisibilityChanged()
: base(nameof(HeadGearVisibilityChanged))
{ }
public void Invoke(Actor actor, ref bool state)
{
var value = new Ref<bool>(state);
Invoke(this, actor, value);
state = value;
}
}

View file

@ -1,4 +1,3 @@
using System;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -11,18 +10,12 @@ namespace Glamourer.Events;
/// <item>Parameter is an array of slots updated and corresponding item ids and stains. </item>
/// </list>
/// </summary>
public sealed class MovedEquipment : EventWrapper<Action<(EquipSlot, uint, StainId)[]>, MovedEquipment.Priority>
public sealed class MovedEquipment()
: EventWrapper<(EquipSlot, uint, StainId)[], MovedEquipment.Priority>(nameof(MovedEquipment))
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnMovedEquipment"/>
StateListener = 0,
}
public MovedEquipment()
: base(nameof(MovedEquipment))
{ }
public void Invoke((EquipSlot, uint, StainId)[] items)
=> Invoke(this, items);
}

View file

@ -11,7 +11,8 @@ namespace Glamourer.Events;
/// <item>Parameter is the timestamp of the unlock. </item>
/// </list>
/// </summary>
public sealed class ObjectUnlocked : EventWrapper<Action<ObjectUnlocked.Type, uint, DateTimeOffset>, ObjectUnlocked.Priority>
public sealed class ObjectUnlocked()
: EventWrapper<ObjectUnlocked.Type, uint, DateTimeOffset, ObjectUnlocked.Priority>(nameof(ObjectUnlocked))
{
public enum Type
{
@ -25,11 +26,4 @@ public sealed class ObjectUnlocked : EventWrapper<Action<ObjectUnlocked.Type, ui
/// <remarks> Currently used as a hack to make the unlock table dirty in it. If anything else starts using this, rework. </remarks>
UnlockTable = 0,
}
public ObjectUnlocked()
: base(nameof(ObjectUnlocked))
{ }
public void Invoke(Type type, uint id, DateTimeOffset timestamp)
=> Invoke(this, type, id, timestamp);
}

View file

@ -1,4 +1,3 @@
using System;
using OtterGui.Classes;
namespace Glamourer.Events;
@ -6,18 +5,12 @@ namespace Glamourer.Events;
/// <summary>
/// Triggered when Penumbra is reloaded.
/// </summary>
public sealed class PenumbraReloaded : EventWrapper<Action, PenumbraReloaded.Priority>
public sealed class PenumbraReloaded()
: EventWrapper<PenumbraReloaded.Priority>(nameof(PenumbraReloaded))
{
public enum Priority
{
/// <seealso cref="Interop.ChangeCustomizeService.Restore"/>
ChangeCustomizeService = 0,
}
public PenumbraReloaded()
: base(nameof(PenumbraReloaded))
{ }
public void Invoke()
=> Invoke(this);
}

View file

@ -15,24 +15,12 @@ namespace Glamourer.Events;
/// <item>Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. </item>
/// </list>
/// </summary>
public sealed class SlotUpdating : EventWrapper<Action<Model, EquipSlot, Ref<CharacterArmor>, Ref<ulong>>, SlotUpdating.Priority>
public sealed class SlotUpdating()
: EventWrapperRef34<Model, EquipSlot, CharacterArmor, ulong, SlotUpdating.Priority>(nameof(SlotUpdating))
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnSlotUpdating"/>
StateListener = 0,
}
public SlotUpdating()
: base(nameof(SlotUpdating))
{ }
public void Invoke(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue)
{
var value = new Ref<CharacterArmor>(armor);
var @return = new Ref<ulong>(returnValue);
Invoke(this, model, slot, value, @return);
armor = value;
returnValue = @return;
}
}

View file

@ -1,8 +1,6 @@
using System;
using Glamourer.Interop.Structs;
using Glamourer.State;
using OtterGui.Classes;
using Penumbra.GameData.Actors;
namespace Glamourer.Events;
@ -15,7 +13,8 @@ namespace Glamourer.Events;
/// <item>Parameter is any additional data depending on the type of change. </item>
/// </list>
/// </summary>
public sealed class StateChanged : EventWrapper<Action<StateChanged.Type, StateChanged.Source, ActorState, ActorData, object?>, StateChanged.Priority>
public sealed class StateChanged()
: EventWrapper<StateChanged.Type, StateChanged.Source, ActorState, ActorData, object?, StateChanged.Priority>(nameof(StateChanged))
{
public enum Type
{
@ -62,11 +61,4 @@ public sealed class StateChanged : EventWrapper<Action<StateChanged.Type, StateC
{
GlamourerIpc = int.MinValue,
}
public StateChanged()
: base(nameof(StateChanged))
{ }
public void Invoke(Type type, Source source, ActorState state, ActorData actors, object? data = null)
=> Invoke(this, type, source, state, actors, data);
}

View file

@ -1,5 +1,4 @@
using System;
using Glamourer.Designs;
using Glamourer.Designs;
using Glamourer.Gui;
using OtterGui.Classes;
@ -12,8 +11,8 @@ namespace Glamourer.Events;
/// <item>Parameter is the design to select if the tab is the designs tab. </item>
/// </list>
/// </summary>
public sealed class TabSelected : EventWrapper<Action<MainWindow.TabType, Design?>,
TabSelected.Priority>
public sealed class TabSelected()
: EventWrapper<MainWindow.TabType, Design?, TabSelected.Priority>(nameof(TabSelected))
{
public enum Priority
{
@ -23,11 +22,4 @@ public sealed class TabSelected : EventWrapper<Action<MainWindow.TabType, Design
/// <seealso cref="Gui.MainWindow.OnTabSelected"/>
MainWindow = 1,
}
public TabSelected()
: base(nameof(TabSelected))
{ }
public void Invoke(MainWindow.TabType type, Design? design)
=> Invoke(this, type, design);
}

View file

@ -1,4 +1,3 @@
using System;
using Glamourer.Interop.Structs;
using OtterGui.Classes;
@ -12,22 +11,12 @@ namespace Glamourer.Events;
/// <item>Parameter is whether to call the original function. </item>
/// </list>
/// </summary>
public sealed class VisorStateChanged : EventWrapper<Action<Model, Ref<bool>>, VisorStateChanged.Priority>
public sealed class VisorStateChanged()
: EventWrapperRef2<Model, bool, VisorStateChanged.Priority>(nameof(VisorStateChanged))
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnVisorChange"/>
StateListener = 0,
}
public VisorStateChanged()
: base(nameof(VisorStateChanged))
{ }
public void Invoke(Model model, ref bool state)
{
var value = new Ref<bool>(state);
Invoke(this, model, value);
state = value;
}
}

View file

@ -1,4 +1,3 @@
using System;
using Glamourer.Interop.Structs;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
@ -14,7 +13,8 @@ namespace Glamourer.Events;
/// <item>Parameter is the model values to change the weapon to. </item>
/// </list>
/// </summary>
public sealed class WeaponLoading : EventWrapper<Action<Actor, EquipSlot, Ref<CharacterWeapon>>, WeaponLoading.Priority>
public sealed class WeaponLoading()
: EventWrapperRef3<Actor, EquipSlot, CharacterWeapon, WeaponLoading.Priority>(nameof(WeaponLoading))
{
public enum Priority
{
@ -24,15 +24,4 @@ public sealed class WeaponLoading : EventWrapper<Action<Actor, EquipSlot, Ref<Ch
/// <seealso cref="Automation.AutoDesignApplier.OnWeaponLoading"/>
AutoDesignApplier = -1,
}
public WeaponLoading()
: base(nameof(WeaponLoading))
{ }
public void Invoke(Actor actor, EquipSlot slot, ref CharacterWeapon weapon)
{
var value = new Ref<CharacterWeapon>(weapon);
Invoke(this, actor, slot, value);
weapon = value;
}
}

View file

@ -1,4 +1,3 @@
using System;
using Glamourer.Interop.Structs;
using OtterGui.Classes;
@ -11,22 +10,11 @@ namespace Glamourer.Events;
/// <item>Parameter is the new state. </item>
/// </list>
/// </summary>
public sealed class WeaponVisibilityChanged : EventWrapper<Action<Actor, Ref<bool>>, WeaponVisibilityChanged.Priority>
public sealed class WeaponVisibilityChanged() : EventWrapperRef2<Actor, bool, WeaponVisibilityChanged.Priority>(nameof(WeaponVisibilityChanged))
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnWeaponVisibilityChange"/>
StateListener = 0,
}
public WeaponVisibilityChanged()
: base(nameof(WeaponVisibilityChanged))
{ }
public void Invoke(Actor actor, ref bool state)
{
var value = new Ref<bool>(state);
Invoke(this, actor, value);
state = value;
}
}

View file

@ -5,7 +5,6 @@ using Glamourer.Gui;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.State;
using Microsoft.Extensions.DependencyInjection;
using OtterGui.Classes;
using OtterGui.Log;
using OtterGui.Services;
@ -34,6 +33,8 @@ public class Glamourer : IDalamudPlugin
{
_services = ServiceManagerA.CreateProvider(pluginInterface, Log);
Messager = _services.GetService<MessageService>();
_services.EnsureRequiredServices();
_services.GetService<VisorService>();
_services.GetService<WeaponService>();
_services.GetService<ScalingService>();

View file

@ -14,7 +14,7 @@ namespace Glamourer.Interop;
/// Changes in Race, body type or Gender are probably ignored.
/// This operates on draw objects, not game objects.
/// </summary>
public unsafe class ChangeCustomizeService : EventWrapper<Action<Model, Ref<CustomizeArray>>, ChangeCustomizeService.Priority>
public unsafe class ChangeCustomizeService : EventWrapperRef2<Model, CustomizeArray, ChangeCustomizeService.Priority>
{
private readonly PenumbraReloaded _penumbraReloaded;
private readonly IGameInteropProvider _interop;
@ -79,11 +79,7 @@ public unsafe class ChangeCustomizeService : EventWrapper<Action<Model, Ref<Cust
private bool ChangeCustomizeDetour(Human* human, byte* data, byte skipEquipment)
{
if (!InUpdate.InMethod)
{
var customize = new Ref<CustomizeArray>(*(CustomizeArray*)data);
Invoke(this, (Model)human, customize);
*(CustomizeArray*)data = customize.Value;
}
Invoke(human, ref *(CustomizeArray*)data);
return _changeCustomizeHook.Original(human, data, skipEquipment);
}

View file

@ -1,5 +1,4 @@
using System;
using Dalamud.Hooking;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
@ -15,10 +14,10 @@ namespace Glamourer.Interop;
/// <list type="number">
/// <item>Parameter is the model with an update. </item>
/// <item>Parameter is the equipment slot changed. </item>
/// <item>Parameter is the whether the crest will be shown. </item>
/// <item>Parameter is whether the crest will be shown. </item>
/// </list>
/// </summary>
public sealed unsafe class CrestService : EventWrapper<Action<Actor, CrestFlag, Ref<bool>>, CrestService.Priority>
public sealed unsafe class CrestService : EventWrapperRef3<Actor, CrestFlag, bool, CrestService.Priority>
{
public enum Priority
{
@ -72,9 +71,9 @@ public sealed unsafe class CrestService : EventWrapper<Action<Actor, CrestFlag,
var actor = (Actor)character;
foreach (var slot in CrestExtensions.AllRelevantSet)
{
var newValue = new Ref<bool>(((CrestFlag)crestFlags).HasFlag(slot));
Invoke(this, actor, slot, newValue);
crestFlags = (byte)(newValue.Value ? crestFlags | (byte)slot : crestFlags & (byte)~slot);
var newValue = ((CrestFlag)crestFlags).HasFlag(slot);
Invoke(actor, slot, ref newValue);
crestFlags = (byte)(newValue ? crestFlags | (byte)slot : crestFlags & (byte)~slot);
}
Glamourer.Log.Verbose(

View file

@ -33,7 +33,7 @@ public static class ServiceManagerA
{
public static ServiceManager CreateProvider(DalamudPluginInterface pi, Logger log)
{
EventWrapper.ChangeLogger(log);
EventWrapperBase.ChangeLogger(log);
var services = new ServiceManager(log)
.AddExistingService(log)
.AddMeta()

View file

@ -138,7 +138,7 @@ public class StateListener : IDisposable
ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender);
}
private unsafe void OnCustomizeChange(Model model, Ref<CustomizeArray> customize)
private unsafe void OnCustomizeChange(Model model, ref CustomizeArray customize)
{
if (!model.IsHuman)
return;
@ -151,7 +151,7 @@ public class StateListener : IDisposable
|| !_manager.TryGetValue(identifier, out var state))
return;
UpdateCustomize(actor, state, ref customize.Value, false);
UpdateCustomize(actor, state, ref customize, false);
}
private void UpdateCustomize(Actor actor, ActorState state, ref CustomizeArray customize, bool checkTransform)
@ -199,7 +199,7 @@ public class StateListener : IDisposable
/// A draw model loads a new equipment piece.
/// Update base data, apply or update model data, and protect against restricted gear.
/// </summary>
private void OnSlotUpdating(Model model, EquipSlot slot, Ref<CharacterArmor> armor, Ref<ulong> returnValue)
private void OnSlotUpdating(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue)
{
var actor = _penumbra.GameObjectFromDrawObject(model);
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
@ -212,16 +212,16 @@ public class StateListener : IDisposable
if (actor.Identifier(_actors, out var identifier)
&& _manager.TryGetValue(identifier, out var state))
{
HandleEquipSlot(actor, state, slot, ref armor.Value);
HandleEquipSlot(actor, state, slot, ref armor);
locked = state[slot, false] is StateChanged.Source.Ipc;
}
_funModule.ApplyFunToSlot(actor, ref armor.Value, slot);
_funModule.ApplyFunToSlot(actor, ref armor, slot);
if (!_config.UseRestrictedGearProtection || locked)
return;
var customize = model.GetCustomize();
(_, armor.Value) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
(_, armor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
}
private void OnMovedEquipment((EquipSlot, uint, StainId)[] items)
@ -264,20 +264,20 @@ public class StateListener : IDisposable
/// Update base data, apply or update model data.
/// Verify consistent weapon types.
/// </summary>
private void OnWeaponLoading(Actor actor, EquipSlot slot, Ref<CharacterWeapon> weapon)
private void OnWeaponLoading(Actor actor, EquipSlot slot, ref CharacterWeapon weapon)
{
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
return;
// Fist weapon gauntlet hack.
if (slot is EquipSlot.OffHand && weapon.Value.Variant == 0 && weapon.Value.Weapon.Id != 0 && _lastFistOffhand.Weapon.Id != 0)
weapon.Value = _lastFistOffhand;
if (slot is EquipSlot.OffHand && weapon.Variant == 0 && weapon.Weapon.Id != 0 && _lastFistOffhand.Weapon.Id != 0)
weapon = _lastFistOffhand;
if (!actor.Identifier(_actors, out var identifier)
|| !_manager.TryGetValue(identifier, out var state))
return;
ref var actorWeapon = ref weapon.Value;
ref var actorWeapon = ref weapon;
var baseType = state.BaseData.Item(slot).Type;
var apply = false;
switch (UpdateBaseData(actor, state, slot, actorWeapon))
@ -311,25 +311,16 @@ public class StateListener : IDisposable
}
// Fist Weapon Offhand hack.
if (slot is EquipSlot.MainHand && weapon.Value.Skeleton.Id is > 1600 and < 1651)
_lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Value.Skeleton.Id + 50), weapon.Value.Weapon, weapon.Value.Variant,
weapon.Value.Stain);
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);
_funModule.ApplyFunToWeapon(actor, ref weapon.Value, slot);
_funModule.ApplyFunToWeapon(actor, ref weapon, slot);
}
/// <summary> Update base data for a single changed equipment slot. </summary>
private UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterArmor armor)
{
bool FistWeaponGauntletHack()
{
if (slot is not EquipSlot.Hands)
return false;
var offhand = actor.GetOffhand();
return offhand.Variant == 0 && offhand.Weapon.Id != 0 && armor.Set.Id == offhand.Weapon.Id;
}
var actorArmor = actor.GetArmor(slot);
var fistWeapon = FistWeaponGauntletHack();
@ -373,6 +364,15 @@ public class StateListener : IDisposable
}
return change;
bool FistWeaponGauntletHack()
{
if (slot is not EquipSlot.Hands)
return false;
var offhand = actor.GetOffhand();
return offhand.Variant == 0 && offhand.Weapon.Id != 0 && armor.Set.Id == offhand.Weapon.Id;
}
}
/// <summary> Handle a full equip slot update for base data and model data. </summary>
@ -406,7 +406,7 @@ public class StateListener : IDisposable
}
}
private void OnCrestChange(Actor actor, CrestFlag slot, Ref<bool> value)
private void OnCrestChange(Actor actor, CrestFlag slot, ref bool value)
{
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
return;
@ -415,17 +415,17 @@ public class StateListener : IDisposable
|| !_manager.TryGetValue(identifier, out var state))
return;
switch (UpdateBaseCrest(actor, state, slot, value.Value))
switch (UpdateBaseCrest(actor, state, slot, value))
{
case UpdateState.Change:
if (state[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game);
else
value.Value = state.ModelData.Crest(slot);
value = state.ModelData.Crest(slot);
break;
case UpdateState.NoChange:
case UpdateState.HatHack:
value.Value = state.ModelData.Crest(slot);
value = state.ModelData.Crest(slot);
break;
case UpdateState.Transformed: break;
}
@ -540,7 +540,7 @@ public class StateListener : IDisposable
}
/// <summary> Handle visor state changes made by the game. </summary>
private void OnVisorChange(Model model, Ref<bool> value)
private void OnVisorChange(Model model, ref bool value)
{
// Skip updates when in customize update.
if (ChangeCustomizeService.InUpdate.InMethod)
@ -565,19 +565,19 @@ public class StateListener : IDisposable
// if base state changed, either overwrite the actual value if we have fixed values,
// or overwrite the stored model state with the new one.
if (state[ActorState.MetaIndex.VisorState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc)
value.Value = state.ModelData.IsVisorToggled();
value = state.ModelData.IsVisorToggled();
else
_manager.ChangeVisorState(state, value, StateChanged.Source.Game);
}
else
{
// if base state did not change, overwrite the value with the model state one.
value.Value = state.ModelData.IsVisorToggled();
value = state.ModelData.IsVisorToggled();
}
}
/// <summary> Handle Hat Visibility changes. These act on the game object. </summary>
private void OnHeadGearVisibilityChange(Actor actor, Ref<bool> value)
private void OnHeadGearVisibilityChange(Actor actor, ref bool value)
{
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
return;
@ -598,19 +598,19 @@ public class StateListener : IDisposable
// if base state changed, either overwrite the actual value if we have fixed values,
// or overwrite the stored model state with the new one.
if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc)
value.Value = state.ModelData.IsHatVisible();
value = state.ModelData.IsHatVisible();
else
_manager.ChangeHatState(state, value, StateChanged.Source.Game);
}
else
{
// if base state did not change, overwrite the value with the model state one.
value.Value = state.ModelData.IsHatVisible();
value = state.ModelData.IsHatVisible();
}
}
/// <summary> Handle Weapon Visibility changes. These act on the game object. </summary>
private void OnWeaponVisibilityChange(Actor actor, Ref<bool> value)
private void OnWeaponVisibilityChange(Actor actor, ref bool value)
{
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
return;
@ -631,14 +631,14 @@ public class StateListener : IDisposable
// if base state changed, either overwrite the actual value if we have fixed values,
// or overwrite the stored model state with the new one.
if (state[ActorState.MetaIndex.WeaponState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc)
value.Value = state.ModelData.IsWeaponVisible();
value = state.ModelData.IsWeaponVisible();
else
_manager.ChangeWeaponState(state, value, StateChanged.Source.Game);
}
else
{
// if base state did not change, overwrite the value with the model state one.
value.Value = state.ModelData.IsWeaponVisible();
value = state.ModelData.IsWeaponVisible();
}
}

View file

@ -476,7 +476,7 @@ public class StateManager(
actors = 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, StateChanged.Source.Manual, state, actors);
_event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors, null);
}
public void ResetStateFixed(ActorState state, uint key = 0)

@ -1 +1 @@
Subproject commit e58c3c1240cda9d2d2b54f5ab7b8c729c1251fd4
Subproject commit e4a82619332aade2748a381956a1ab8934de6211