This commit is contained in:
Ottermandias 2023-06-22 12:15:44 +02:00
parent 803fd1b247
commit 1aba34f34a
11 changed files with 631 additions and 32 deletions

View file

@ -0,0 +1,318 @@
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using System;
using Glamourer.Api;
using Glamourer.Designs;
using Glamourer.Services;
using Glamourer.State;
using Penumbra.Api.Enums;
namespace Glamourer;
public partial class Glamourer
{
public class GlamourerIpc : IDisposable
{
public const int CurrentApiVersion = 0;
public const string LabelProviderApiVersion = "Glamourer.ApiVersion";
public const string LabelProviderGetAllCustomization = "Glamourer.GetAllCustomization";
public const string LabelProviderGetAllCustomizationFromCharacter = "Glamourer.GetAllCustomizationFromCharacter";
public const string LabelProviderApplyAll = "Glamourer.ApplyAll";
public const string LabelProviderApplyAllToCharacter = "Glamourer.ApplyAllToCharacter";
public const string LabelProviderApplyOnlyEquipment = "Glamourer.ApplyOnlyEquipment";
public const string LabelProviderApplyOnlyEquipmentToCharacter = "Glamourer.ApplyOnlyEquipmentToCharacter";
public const string LabelProviderApplyOnlyCustomization = "Glamourer.ApplyOnlyCustomization";
public const string LabelProviderApplyOnlyCustomizationToCharacter = "Glamourer.ApplyOnlyCustomizationToCharacter";
public const string LabelProviderRevert = "Glamourer.Revert";
public const string LabelProviderRevertCharacter = "Glamourer.RevertCharacter";
private readonly ObjectTable _objectTable;
private readonly DalamudPluginInterface _pluginInterface;
private readonly ActiveDesign.Manager _stateManager;
private readonly ItemManager _items;
private readonly PenumbraAttach _penumbra;
private readonly ActorService _actors;
internal ICallGateProvider<string, string?>? ProviderGetAllCustomization;
internal ICallGateProvider<Character?, string?>? ProviderGetAllCustomizationFromCharacter;
internal ICallGateProvider<string, string, object>? ProviderApplyAll;
internal ICallGateProvider<string, Character?, object>? ProviderApplyAllToCharacter;
internal ICallGateProvider<string, string, object>? ProviderApplyOnlyCustomization;
internal ICallGateProvider<string, Character?, object>? ProviderApplyOnlyCustomizationToCharacter;
internal ICallGateProvider<string, string, object>? ProviderApplyOnlyEquipment;
internal ICallGateProvider<string, Character?, object>? ProviderApplyOnlyEquipmentToCharacter;
internal ICallGateProvider<string, object>? ProviderRevert;
internal ICallGateProvider<Character?, object>? ProviderRevertCharacter;
internal ICallGateProvider<int>? ProviderGetApiVersion;
public GlamourerIpc(ObjectTable objectTable, DalamudPluginInterface pluginInterface, ActiveDesign.Manager stateManager,
ItemManager items, PenumbraAttach penumbra, ActorService actors)
{
_objectTable = objectTable;
_pluginInterface = pluginInterface;
_stateManager = stateManager;
_items = items;
_penumbra = penumbra;
_actors = actors;
InitializeProviders();
}
public void Dispose()
=> DisposeProviders();
private void DisposeProviders()
{
ProviderGetAllCustomization?.UnregisterFunc();
ProviderGetAllCustomizationFromCharacter?.UnregisterFunc();
ProviderApplyAll?.UnregisterAction();
ProviderApplyAllToCharacter?.UnregisterAction();
ProviderApplyOnlyCustomization?.UnregisterAction();
ProviderApplyOnlyCustomizationToCharacter?.UnregisterAction();
ProviderApplyOnlyEquipment?.UnregisterAction();
ProviderApplyOnlyEquipmentToCharacter?.UnregisterAction();
ProviderRevert?.UnregisterAction();
ProviderRevertCharacter?.UnregisterAction();
ProviderGetApiVersion?.UnregisterFunc();
}
private void InitializeProviders()
{
try
{
ProviderGetApiVersion = _pluginInterface.GetIpcProvider<int>(LabelProviderApiVersion);
ProviderGetApiVersion.RegisterFunc(GetApiVersion);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApiVersion}.");
}
try
{
ProviderGetAllCustomization = _pluginInterface.GetIpcProvider<string, string?>(LabelProviderGetAllCustomization);
ProviderGetAllCustomization.RegisterFunc(GetAllCustomization);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}.");
}
try
{
ProviderGetAllCustomizationFromCharacter =
_pluginInterface.GetIpcProvider<Character?, string?>(LabelProviderGetAllCustomizationFromCharacter);
ProviderGetAllCustomizationFromCharacter.RegisterFunc(GetAllCustomization);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderGetAllCustomizationFromCharacter}.");
}
try
{
ProviderApplyAll =
_pluginInterface.GetIpcProvider<string, string, object>(LabelProviderApplyAll);
ProviderApplyAll.RegisterAction(ApplyAll);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}.");
}
try
{
ProviderApplyAllToCharacter =
_pluginInterface.GetIpcProvider<string, Character?, object>(LabelProviderApplyAllToCharacter);
ProviderApplyAllToCharacter.RegisterAction(ApplyAll);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}.");
}
try
{
ProviderApplyOnlyCustomization =
_pluginInterface.GetIpcProvider<string, string, object>(LabelProviderApplyOnlyCustomization);
ProviderApplyOnlyCustomization.RegisterAction(ApplyOnlyCustomization);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}.");
}
try
{
ProviderApplyOnlyCustomizationToCharacter =
_pluginInterface.GetIpcProvider<string, Character?, object>(LabelProviderApplyOnlyCustomizationToCharacter);
ProviderApplyOnlyCustomizationToCharacter.RegisterAction(ApplyOnlyCustomization);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}.");
}
try
{
ProviderApplyOnlyEquipment =
_pluginInterface.GetIpcProvider<string, string, object>(LabelProviderApplyOnlyEquipment);
ProviderApplyOnlyEquipment.RegisterAction(ApplyOnlyEquipment);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}.");
}
try
{
ProviderApplyOnlyEquipmentToCharacter =
_pluginInterface.GetIpcProvider<string, Character?, object>(LabelProviderApplyOnlyEquipmentToCharacter);
ProviderApplyOnlyEquipmentToCharacter.RegisterAction(ApplyOnlyEquipment);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}.");
}
try
{
ProviderRevert =
_pluginInterface.GetIpcProvider<string, object>(LabelProviderRevert);
ProviderRevert.RegisterAction(Revert);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderRevert}.");
}
try
{
ProviderRevertCharacter =
_pluginInterface.GetIpcProvider<Character?, object>(LabelProviderRevertCharacter);
ProviderRevertCharacter.RegisterAction(Revert);
}
catch (Exception ex)
{
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderRevert}.");
}
}
private static int GetApiVersion()
=> CurrentApiVersion;
private void ApplyAll(string customization, string characterName)
{
foreach (var gameObject in _objectTable)
{
if (gameObject.Name.ToString() == characterName)
{
ApplyAll(customization, gameObject as Character);
return;
}
}
}
private void ApplyAll(string customization, Character? character)
{
if (character == null)
return;
var design = Design.CreateTemporaryFromBase64(_items, customization, true, true);
var active = _stateManager.GetOrCreateSave(character.Address);
_stateManager.ApplyDesign(active, design, false);
}
private void ApplyOnlyCustomization(string customization, string characterName)
{
foreach (var gameObject in _objectTable)
{
if (gameObject.Name.ToString() == characterName)
{
ApplyOnlyCustomization(customization, gameObject as Character);
return;
}
}
}
private void ApplyOnlyCustomization(string customization, Character? character)
{
if (character == null)
return;
var design = Design.CreateTemporaryFromBase64(_items, customization, true, false);
var active = _stateManager.GetOrCreateSave(character.Address);
_stateManager.ApplyDesign(active, design, false);
}
private void ApplyOnlyEquipment(string customization, string characterName)
{
foreach (var gameObject in _objectTable)
{
if (gameObject.Name.ToString() != characterName)
continue;
ApplyOnlyEquipment(customization, gameObject as Character);
return;
}
}
private void ApplyOnlyEquipment(string customization, Character? character)
{
if (character == null)
return;
var design = Design.CreateTemporaryFromBase64(_items, customization, false, true);
var active = _stateManager.GetOrCreateSave(character.Address);
_stateManager.ApplyDesign(active, design, false);
}
private void Revert(string characterName)
{
foreach (var gameObject in _objectTable)
{
if (gameObject.Name.ToString() != characterName)
continue;
Revert(gameObject as Character);
}
}
private void Revert(Character? character)
{
if (character == null)
return;
var ident = _actors.AwaitedService.FromObject(character, true, false, false);
_stateManager.DeleteSave(ident);
_penumbra.RedrawObject(character.Address, RedrawType.Redraw);
}
private string? GetAllCustomization(Character? character)
{
if (character == null)
return null;
var ident = _actors.AwaitedService.FromObject(character, true, false, false);
if (!_stateManager.TryGetValue(ident, out var design))
design = new ActiveDesign(_items, ident, character.Address);
return design.CreateOldBase64();
}
private string? GetAllCustomization(string characterName)
{
foreach (var gameObject in _objectTable)
{
if (gameObject.Name.ToString() == characterName)
return GetAllCustomization(gameObject as Character);
}
return null;
}
}
}

View file

@ -0,0 +1,32 @@
using System;
using Glamourer.Interop.Structs;
using OtterGui.Classes;
namespace Glamourer.Events;
/// <summary>
/// Triggered when the visibility of head gear is changed.
/// <list type="number">
/// <item>Parameter is the actor with changed head gear visibility. </item>
/// <item>Parameter is the new state. </item>
/// </list>
/// </summary>
public sealed class HeadGearVisibilityChanged : EventWrapper<Action<Actor, Ref<bool>>, HeadGearVisibilityChanged.Priority>
{
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

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

View file

@ -0,0 +1,32 @@
using System;
using Glamourer.Interop.Structs;
using OtterGui.Classes;
namespace Glamourer.Events;
/// <summary>
/// Triggered when the visibility of weapons is changed.
/// <list type="number">
/// <item>Parameter is the actor with changed weapon visibility. </item>
/// <item>Parameter is the new state. </item>
/// </list>
/// </summary>
public sealed class WeaponVisibilityChanged : EventWrapper<Action<Actor, Ref<bool>>, WeaponVisibilityChanged.Priority>
{
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

@ -32,6 +32,7 @@ public unsafe class DebugTab : ITab
private readonly ChangeCustomizeService _changeCustomizeService; private readonly ChangeCustomizeService _changeCustomizeService;
private readonly UpdateSlotService _updateSlotService; private readonly UpdateSlotService _updateSlotService;
private readonly WeaponService _weaponService; private readonly WeaponService _weaponService;
private readonly MetaService _metaService;
private readonly PenumbraService _penumbra; private readonly PenumbraService _penumbra;
private readonly ObjectTable _objects; private readonly ObjectTable _objects;
private readonly ObjectManager _objectManager; private readonly ObjectManager _objectManager;
@ -56,7 +57,7 @@ public unsafe class DebugTab : ITab
UpdateSlotService updateSlotService, WeaponService weaponService, PenumbraService penumbra, UpdateSlotService updateSlotService, WeaponService weaponService, PenumbraService penumbra,
ActorService actors, ItemManager items, CustomizationService customization, ObjectManager objectManager, ActorService actors, ItemManager items, CustomizationService customization, ObjectManager objectManager,
DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config, DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config,
PenumbraChangedItemTooltip penumbraTooltip) PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService)
{ {
_changeCustomizeService = changeCustomizeService; _changeCustomizeService = changeCustomizeService;
_visorService = visorService; _visorService = visorService;
@ -73,6 +74,7 @@ public unsafe class DebugTab : ITab
_state = state; _state = state;
_config = config; _config = config;
_penumbraTooltip = penumbraTooltip; _penumbraTooltip = penumbraTooltip;
_metaService = metaService;
} }
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label

View file

@ -0,0 +1,72 @@
using System;
using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Glamourer.Events;
using Glamourer.Interop.Structs;
using static OtterGui.Raii.ImRaii;
namespace Glamourer.Interop;
public unsafe class MetaService : IDisposable
{
private readonly HeadGearVisibilityChanged _headGearEvent;
private readonly WeaponVisibilityChanged _weaponEvent;
private delegate void HideHatGearDelegate(DrawDataContainer* drawData, uint id, byte value);
private delegate void HideWeaponsDelegate(DrawDataContainer* drawData, bool value);
private readonly Hook<HideHatGearDelegate> _hideHatGearHook;
private readonly Hook<HideWeaponsDelegate> _hideWeaponsHook;
public MetaService(WeaponVisibilityChanged weaponEvent, HeadGearVisibilityChanged headGearEvent)
{
_weaponEvent = weaponEvent;
_headGearEvent = headGearEvent;
_hideHatGearHook = Hook<HideHatGearDelegate>.FromAddress((nint)DrawDataContainer.MemberFunctionPointers.HideHeadgear, HideHatDetour);
_hideWeaponsHook = Hook<HideWeaponsDelegate>.FromAddress((nint)DrawDataContainer.MemberFunctionPointers.HideWeapons, HideWeaponsDetour);
_hideHatGearHook.Enable();
_hideWeaponsHook.Enable();
}
public void Dispose()
{
_hideHatGearHook.Dispose();
_hideWeaponsHook.Dispose();
}
public void SetHatState(Actor actor, bool value)
{
if (!actor.IsCharacter)
return;
_hideHatGearHook.Original(&actor.AsCharacter->DrawData, 0, (byte) (value ? 1 : 0));
}
public void SetWeaponState(Actor actor, bool value)
{
if (!actor.IsCharacter)
return;
_hideWeaponsHook.Original(&actor.AsCharacter->DrawData, value);
}
private void HideHatDetour(DrawDataContainer* drawData, uint id, byte value)
{
Actor actor = drawData->Parent;
var v = value == 0;
_headGearEvent.Invoke(actor, ref v);
value = (byte) (v ? 0 : 1);
Glamourer.Log.Information($"[MetaService] Hide Hat triggered with 0x{(nint)drawData:X} {id} {value} for {actor.Utf8Name}.");
_hideHatGearHook.Original(drawData, id, value);
}
private void HideWeaponsDetour(DrawDataContainer* drawData, bool value)
{
Actor actor = drawData->Parent;
value = !value;
_weaponEvent.Invoke(actor, ref value);
value = !value;
Glamourer.Log.Information($"[MetaService] Hide Weapon triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}.");
_hideWeaponsHook.Original(drawData, value);
}
}

View file

@ -4,6 +4,7 @@ using Dalamud.Hooking;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Penumbra.GameData.Enums;
namespace Glamourer.Interop; namespace Glamourer.Interop;
@ -39,7 +40,7 @@ public class VisorService : IDisposable
if (oldState == on) if (oldState == on)
return false; return false;
SetupVisorHook(human, (ushort)human.AsHuman->HeadSetID, on); SetupVisorHook(human, human.GetArmor(EquipSlot.Head).Set.Value, on);
return true; return true;
} }
@ -50,17 +51,15 @@ public class VisorService : IDisposable
private void SetupVisorDetour(nint human, ushort modelId, bool on) private void SetupVisorDetour(nint human, ushort modelId, bool on)
{ {
var callOriginal = true;
var originalOn = on; var originalOn = on;
// Invoke an event that can change the requested value // Invoke an event that can change the requested value
// and also control whether the function should be called at all. // and also control whether the function should be called at all.
Event.Invoke(human, ref on, ref callOriginal); Event.Invoke(human, ref on);
Glamourer.Log.Excessive( Glamourer.Log.Excessive(
$"[SetVisorState] Invoked from game on 0x{human:X} switching to {on} (original {originalOn}, call original {callOriginal})."); $"[SetVisorState] Invoked from game on 0x{human:X} switching to {on} (original {originalOn}).");
if (callOriginal) SetupVisorHook(human, modelId, on);
SetupVisorHook(human, modelId, on);
} }
/// <summary> /// <summary>

View file

@ -54,7 +54,9 @@ public static class ServiceManager
.AddSingleton<SlotUpdating>() .AddSingleton<SlotUpdating>()
.AddSingleton<DesignChanged>() .AddSingleton<DesignChanged>()
.AddSingleton<StateChanged>() .AddSingleton<StateChanged>()
.AddSingleton<WeaponLoading>(); .AddSingleton<WeaponLoading>()
.AddSingleton<HeadGearVisibilityChanged>()
.AddSingleton<WeaponVisibilityChanged>();
private static IServiceCollection AddData(this IServiceCollection services) private static IServiceCollection AddData(this IServiceCollection services)
=> services.AddSingleton<IdentifierService>() => services.AddSingleton<IdentifierService>()
@ -66,6 +68,7 @@ public static class ServiceManager
private static IServiceCollection AddInterop(this IServiceCollection services) private static IServiceCollection AddInterop(this IServiceCollection services)
=> services.AddSingleton<VisorService>() => services.AddSingleton<VisorService>()
.AddSingleton<ChangeCustomizeService>() .AddSingleton<ChangeCustomizeService>()
.AddSingleton<MetaService>()
.AddSingleton<UpdateSlotService>() .AddSingleton<UpdateSlotService>()
.AddSingleton<WeaponService>() .AddSingleton<WeaponService>()
.AddSingleton<PenumbraService>() .AddSingleton<PenumbraService>()

View file

@ -12,13 +12,16 @@ namespace Glamourer.State;
public class StateListener : IDisposable public class StateListener : IDisposable
{ {
private readonly Configuration _config; private readonly Configuration _config;
private readonly ActorService _actors; private readonly ActorService _actors;
private readonly StateManager _manager; private readonly StateManager _manager;
private readonly ItemManager _items; private readonly ItemManager _items;
private readonly PenumbraService _penumbra; private readonly PenumbraService _penumbra;
private readonly SlotUpdating _slotUpdating; private readonly SlotUpdating _slotUpdating;
private readonly WeaponLoading _weaponLoading; private readonly WeaponLoading _weaponLoading;
private readonly HeadGearVisibilityChanged _headGearVisibility;
private readonly VisorStateChanged _visorState;
private readonly WeaponVisibilityChanged _weaponVisibility;
public bool Enabled public bool Enabled
{ {
@ -27,15 +30,19 @@ public class StateListener : IDisposable
} }
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config, public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config,
SlotUpdating slotUpdating, WeaponLoading weaponLoading) SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
HeadGearVisibilityChanged headGearVisibility)
{ {
_manager = manager; _manager = manager;
_items = items; _items = items;
_penumbra = penumbra; _penumbra = penumbra;
_actors = actors; _actors = actors;
_config = config; _config = config;
_slotUpdating = slotUpdating; _slotUpdating = slotUpdating;
_weaponLoading = weaponLoading; _weaponLoading = weaponLoading;
_visorState = visorState;
_weaponVisibility = weaponVisibility;
_headGearVisibility = headGearVisibility;
if (Enabled) if (Enabled)
Subscribe(); Subscribe();
@ -253,6 +260,9 @@ public class StateListener : IDisposable
_penumbra.CreatingCharacterBase += OnCreatingCharacterBase; _penumbra.CreatingCharacterBase += OnCreatingCharacterBase;
_slotUpdating.Subscribe(OnSlotUpdating, SlotUpdating.Priority.StateListener); _slotUpdating.Subscribe(OnSlotUpdating, SlotUpdating.Priority.StateListener);
_weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener); _weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener);
_visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener);
_headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener);
_weaponVisibility.Subscribe(OnWeaponVisibilityChange, WeaponVisibilityChanged.Priority.StateListener);
} }
private void Unsubscribe() private void Unsubscribe()
@ -260,17 +270,19 @@ public class StateListener : IDisposable
_penumbra.CreatingCharacterBase -= OnCreatingCharacterBase; _penumbra.CreatingCharacterBase -= OnCreatingCharacterBase;
_slotUpdating.Unsubscribe(OnSlotUpdating); _slotUpdating.Unsubscribe(OnSlotUpdating);
_weaponLoading.Unsubscribe(OnWeaponLoading); _weaponLoading.Unsubscribe(OnWeaponLoading);
_visorState.Unsubscribe(OnVisorChange);
_headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange);
_weaponVisibility.Unsubscribe(OnWeaponVisibilityChange);
} }
private UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterArmor armor) private UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterArmor armor)
{ {
var actorArmor = actor.GetArmor(slot); var actorArmor = actor.GetArmor(slot);
// The actor armor does not correspond to the model armor, thus the actor is transformed. // The actor armor does not correspond to the model armor, thus the actor is transformed.
// This also prevents it from changing values due to hat state.
if (actorArmor.Value != armor.Value) if (actorArmor.Value != armor.Value)
return UpdateState.Transformed; return UpdateState.Transformed;
// TODO: Hat State.
var baseData = state.BaseData.Armor(slot); var baseData = state.BaseData.Armor(slot);
var change = UpdateState.NoChange; var change = UpdateState.NoChange;
if (baseData.Stain != armor.Stain) if (baseData.Stain != armor.Stain)
@ -338,4 +350,68 @@ public class StateListener : IDisposable
state.BaseData.Customize.Load(customize); state.BaseData.Customize.Load(customize);
return UpdateState.Change; return UpdateState.Change;
} }
private void OnVisorChange(Model model, Ref<bool> value)
{
var actor = _penumbra.GameObjectFromDrawObject(model);
if (!actor.Identifier(_actors.AwaitedService, out var identifier))
return;
if (!_manager.TryGetValue(identifier, out var state))
return;
if (state.BaseData.SetVisor(value))
{
if (state[ActorState.MetaFlag.VisorState] is StateChanged.Source.Fixed)
value.Value = state.ModelData.IsVisorToggled();
else
_manager.ChangeVisorState(state, value, StateChanged.Source.Game);
}
else
{
value.Value = state.ModelData.IsVisorToggled();
}
}
private void OnHeadGearVisibilityChange(Actor actor, Ref<bool> value)
{
if (!actor.Identifier(_actors.AwaitedService, out var identifier))
return;
if (!_manager.TryGetValue(identifier, out var state))
return;
if (state.BaseData.SetHatVisible(value))
{
if (state[ActorState.MetaFlag.HatState] is StateChanged.Source.Fixed)
value.Value = state.ModelData.IsHatVisible();
else
_manager.ChangeHatState(state, value, StateChanged.Source.Game);
}
else
{
value.Value = state.ModelData.IsHatVisible();
}
}
private void OnWeaponVisibilityChange(Actor actor, Ref<bool> value)
{
if (!actor.Identifier(_actors.AwaitedService, out var identifier))
return;
if (!_manager.TryGetValue(identifier, out var state))
return;
if (state.BaseData.SetWeaponVisible(value))
{
if (state[ActorState.MetaFlag.WeaponState] is StateChanged.Source.Fixed)
value.Value = state.ModelData.IsWeaponVisible();
else
_manager.ChangeWeaponState(state, value, StateChanged.Source.Game);
}
else
{
value.Value = state.ModelData.IsWeaponVisible();
}
}
} }

View file

@ -12,6 +12,7 @@ using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Structs; using Glamourer.Structs;
using Lumina.Excel.GeneratedSheets;
using OtterGui.Log; using OtterGui.Log;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -266,6 +267,14 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
break; break;
} }
} }
if (design.DoApplyHatVisible())
ChangeHatState(state, design.DesignData.IsHatVisible(), StateChanged.Source.Manual);
if (design.DoApplyWeaponVisible())
ChangeWeaponState(state, design.DesignData.IsWeaponVisible(), StateChanged.Source.Manual);
if (design.DoApplyVisorToggle())
ChangeVisorState(state, design.DesignData.IsVisorToggled(), StateChanged.Source.Manual);
if (design.DoApplyWetness())
ChangeWetness(state, design.DesignData.IsWet());
} }
public void ResetState(ActorState state) public void ResetState(ActorState state)
@ -335,6 +344,61 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
_event.Invoke(StateChanged.Type.Stain, source, state, objects, (old, stain, slot)); _event.Invoke(StateChanged.Type.Stain, source, state, objects, (old, stain, slot));
} }
public void ChangeHatState(ActorState state, bool value, StateChanged.Source source)
{
var old = state.ModelData.IsHatVisible();
state.ModelData.SetHatVisible(value);
state[ActorState.MetaFlag.HatState] = source;
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
if (source is StateChanged.Source.Manual)
_editor.ChangeHatState(objects, value);
Glamourer.Log.Verbose(
$"Set Head Gear Visibility in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, source, state, objects, (old, value, ActorState.MetaFlag.HatState));
}
public void ChangeWeaponState(ActorState state, bool value, StateChanged.Source source)
{
var old = state.ModelData.IsWeaponVisible();
state.ModelData.SetWeaponVisible(value);
state[ActorState.MetaFlag.WeaponState] = source;
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
if (source is StateChanged.Source.Manual)
_editor.ChangeWeaponState(objects, value);
Glamourer.Log.Verbose(
$"Set Weapon Visibility in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, source, state, objects, (old, value, ActorState.MetaFlag.WeaponState));
}
public void ChangeVisorState(ActorState state, bool value, StateChanged.Source source)
{
var old = state.ModelData.IsVisorToggled();
state.ModelData.SetVisor(value);
state[ActorState.MetaFlag.VisorState] = source;
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
if (source is StateChanged.Source.Manual)
_editor.ChangeVisor(objects, value);
Glamourer.Log.Verbose(
$"Set Visor State in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, source, state, objects, (old, value, ActorState.MetaFlag.VisorState));
}
public void ChangeWetness(ActorState state, bool value)
{
var old = state.ModelData.IsWet();
state.ModelData.SetIsWet(value);
state[ActorState.MetaFlag.Wetness] = value ? StateChanged.Source.Manual : StateChanged.Source.Game;
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
_editor.ChangeWetness(objects, value);
Glamourer.Log.Verbose(
$"Set Wetness in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, state[ActorState.MetaFlag.Wetness], state, objects, (old, value, ActorState.MetaFlag.Wetness));
}
// //
///// <summary> Change whether to apply a specific customize value. </summary> ///// <summary> Change whether to apply a specific customize value. </summary>
//public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value) //public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)