From 1aba34f34a54aced10b253052e3bf3fea54f017c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 22 Jun 2023 12:15:44 +0200 Subject: [PATCH] . --- Glamourer/Api/GlamourerIpc.cs | 318 ++++++++++++++++++ Glamourer/Events/HeadGearVisibilityChanged.cs | 32 ++ Glamourer/Events/VisorStateChanged.cs | 13 +- Glamourer/Events/WeaponVisibilityChanged.cs | 32 ++ Glamourer/Gui/Tabs/DebugTab.cs | 4 +- Glamourer/Interop/MetaService.cs | 72 ++++ Glamourer/Interop/VisorService.cs | 11 +- Glamourer/Interop/WeaponService.cs | 2 +- Glamourer/Services/ServiceManager.cs | 5 +- Glamourer/State/StateListener.cs | 110 +++++- Glamourer/State/StateManager.cs | 64 ++++ 11 files changed, 631 insertions(+), 32 deletions(-) create mode 100644 Glamourer/Api/GlamourerIpc.cs create mode 100644 Glamourer/Events/HeadGearVisibilityChanged.cs create mode 100644 Glamourer/Events/WeaponVisibilityChanged.cs create mode 100644 Glamourer/Interop/MetaService.cs diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs new file mode 100644 index 0000000..1bdd18a --- /dev/null +++ b/Glamourer/Api/GlamourerIpc.cs @@ -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? ProviderGetAllCustomization; + internal ICallGateProvider? ProviderGetAllCustomizationFromCharacter; + internal ICallGateProvider? ProviderApplyAll; + internal ICallGateProvider? ProviderApplyAllToCharacter; + internal ICallGateProvider? ProviderApplyOnlyCustomization; + internal ICallGateProvider? ProviderApplyOnlyCustomizationToCharacter; + internal ICallGateProvider? ProviderApplyOnlyEquipment; + internal ICallGateProvider? ProviderApplyOnlyEquipmentToCharacter; + internal ICallGateProvider? ProviderRevert; + internal ICallGateProvider? ProviderRevertCharacter; + internal ICallGateProvider? 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(LabelProviderApiVersion); + ProviderGetApiVersion.RegisterFunc(GetApiVersion); + } + catch (Exception ex) + { + PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApiVersion}."); + } + + try + { + ProviderGetAllCustomization = _pluginInterface.GetIpcProvider(LabelProviderGetAllCustomization); + ProviderGetAllCustomization.RegisterFunc(GetAllCustomization); + } + catch (Exception ex) + { + PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}."); + } + + try + { + ProviderGetAllCustomizationFromCharacter = + _pluginInterface.GetIpcProvider(LabelProviderGetAllCustomizationFromCharacter); + ProviderGetAllCustomizationFromCharacter.RegisterFunc(GetAllCustomization); + } + catch (Exception ex) + { + PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderGetAllCustomizationFromCharacter}."); + } + + try + { + ProviderApplyAll = + _pluginInterface.GetIpcProvider(LabelProviderApplyAll); + ProviderApplyAll.RegisterAction(ApplyAll); + } + catch (Exception ex) + { + PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}."); + } + + try + { + ProviderApplyAllToCharacter = + _pluginInterface.GetIpcProvider(LabelProviderApplyAllToCharacter); + ProviderApplyAllToCharacter.RegisterAction(ApplyAll); + } + catch (Exception ex) + { + PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}."); + } + + try + { + ProviderApplyOnlyCustomization = + _pluginInterface.GetIpcProvider(LabelProviderApplyOnlyCustomization); + ProviderApplyOnlyCustomization.RegisterAction(ApplyOnlyCustomization); + } + catch (Exception ex) + { + PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}."); + } + + try + { + ProviderApplyOnlyCustomizationToCharacter = + _pluginInterface.GetIpcProvider(LabelProviderApplyOnlyCustomizationToCharacter); + ProviderApplyOnlyCustomizationToCharacter.RegisterAction(ApplyOnlyCustomization); + } + catch (Exception ex) + { + PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}."); + } + + try + { + ProviderApplyOnlyEquipment = + _pluginInterface.GetIpcProvider(LabelProviderApplyOnlyEquipment); + ProviderApplyOnlyEquipment.RegisterAction(ApplyOnlyEquipment); + } + catch (Exception ex) + { + PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}."); + } + + try + { + ProviderApplyOnlyEquipmentToCharacter = + _pluginInterface.GetIpcProvider(LabelProviderApplyOnlyEquipmentToCharacter); + ProviderApplyOnlyEquipmentToCharacter.RegisterAction(ApplyOnlyEquipment); + } + catch (Exception ex) + { + PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}."); + } + + try + { + ProviderRevert = + _pluginInterface.GetIpcProvider(LabelProviderRevert); + ProviderRevert.RegisterAction(Revert); + } + catch (Exception ex) + { + PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderRevert}."); + } + + try + { + ProviderRevertCharacter = + _pluginInterface.GetIpcProvider(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; + } + } +} diff --git a/Glamourer/Events/HeadGearVisibilityChanged.cs b/Glamourer/Events/HeadGearVisibilityChanged.cs new file mode 100644 index 0000000..d12cd69 --- /dev/null +++ b/Glamourer/Events/HeadGearVisibilityChanged.cs @@ -0,0 +1,32 @@ +using System; +using Glamourer.Interop.Structs; +using OtterGui.Classes; + +namespace Glamourer.Events; + +/// +/// Triggered when the visibility of head gear is changed. +/// +/// Parameter is the actor with changed head gear visibility. +/// Parameter is the new state. +/// +/// +public sealed class HeadGearVisibilityChanged : EventWrapper>, HeadGearVisibilityChanged.Priority> +{ + public enum Priority + { + /// + StateListener = 0, + } + + public HeadGearVisibilityChanged() + : base(nameof(HeadGearVisibilityChanged)) + { } + + public void Invoke(Actor actor, ref bool state) + { + var value = new Ref(state); + Invoke(this, actor, value); + state = value; + } +} diff --git a/Glamourer/Events/VisorStateChanged.cs b/Glamourer/Events/VisorStateChanged.cs index 9872b9b..0cd83d1 100644 --- a/Glamourer/Events/VisorStateChanged.cs +++ b/Glamourer/Events/VisorStateChanged.cs @@ -12,21 +12,22 @@ namespace Glamourer.Events; /// Parameter is whether to call the original function. /// /// -public sealed class VisorStateChanged : EventWrapper, Ref>, VisorStateChanged.Priority> +public sealed class VisorStateChanged : EventWrapper>, VisorStateChanged.Priority> { public enum Priority - { } + { + /// + StateListener = 0, + } public 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(state); - var original = new Ref(callOriginal); - Invoke(this, model, value, original); + Invoke(this, model, value); state = value; - callOriginal = original; } } diff --git a/Glamourer/Events/WeaponVisibilityChanged.cs b/Glamourer/Events/WeaponVisibilityChanged.cs new file mode 100644 index 0000000..561c793 --- /dev/null +++ b/Glamourer/Events/WeaponVisibilityChanged.cs @@ -0,0 +1,32 @@ +using System; +using Glamourer.Interop.Structs; +using OtterGui.Classes; + +namespace Glamourer.Events; + +/// +/// Triggered when the visibility of weapons is changed. +/// +/// Parameter is the actor with changed weapon visibility. +/// Parameter is the new state. +/// +/// +public sealed class WeaponVisibilityChanged : EventWrapper>, WeaponVisibilityChanged.Priority> +{ + public enum Priority + { + /// + StateListener = 0, + } + + public WeaponVisibilityChanged() + : base(nameof(WeaponVisibilityChanged)) + { } + + public void Invoke(Actor actor, ref bool state) + { + var value = new Ref(state); + Invoke(this, actor, value); + state = value; + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index b88b1cb..5db76df 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -32,6 +32,7 @@ public unsafe class DebugTab : ITab private readonly ChangeCustomizeService _changeCustomizeService; private readonly UpdateSlotService _updateSlotService; private readonly WeaponService _weaponService; + private readonly MetaService _metaService; private readonly PenumbraService _penumbra; private readonly ObjectTable _objects; private readonly ObjectManager _objectManager; @@ -56,7 +57,7 @@ public unsafe class DebugTab : ITab UpdateSlotService updateSlotService, WeaponService weaponService, PenumbraService penumbra, ActorService actors, ItemManager items, CustomizationService customization, ObjectManager objectManager, DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config, - PenumbraChangedItemTooltip penumbraTooltip) + PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService) { _changeCustomizeService = changeCustomizeService; _visorService = visorService; @@ -73,6 +74,7 @@ public unsafe class DebugTab : ITab _state = state; _config = config; _penumbraTooltip = penumbraTooltip; + _metaService = metaService; } public ReadOnlySpan Label diff --git a/Glamourer/Interop/MetaService.cs b/Glamourer/Interop/MetaService.cs new file mode 100644 index 0000000..4528abb --- /dev/null +++ b/Glamourer/Interop/MetaService.cs @@ -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 _hideHatGearHook; + private readonly Hook _hideWeaponsHook; + + public MetaService(WeaponVisibilityChanged weaponEvent, HeadGearVisibilityChanged headGearEvent) + { + _weaponEvent = weaponEvent; + _headGearEvent = headGearEvent; + _hideHatGearHook = Hook.FromAddress((nint)DrawDataContainer.MemberFunctionPointers.HideHeadgear, HideHatDetour); + _hideWeaponsHook = Hook.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); + } +} diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index f6bb0ab..981eb55 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -4,6 +4,7 @@ using Dalamud.Hooking; using Dalamud.Utility.Signatures; using Glamourer.Events; using Glamourer.Interop.Structs; +using Penumbra.GameData.Enums; namespace Glamourer.Interop; @@ -39,7 +40,7 @@ public class VisorService : IDisposable if (oldState == on) return false; - SetupVisorHook(human, (ushort)human.AsHuman->HeadSetID, on); + SetupVisorHook(human, human.GetArmor(EquipSlot.Head).Set.Value, on); return true; } @@ -50,17 +51,15 @@ public class VisorService : IDisposable private void SetupVisorDetour(nint human, ushort modelId, bool on) { - var callOriginal = true; var originalOn = on; // Invoke an event that can change the requested value // 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( - $"[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); } /// diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 84c9037..96f25bb 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -92,4 +92,4 @@ public unsafe class WeaponService : IDisposable var weapon = new CharacterWeapon(value.Value) { Stain = stain.Value }; LoadWeapon(character, slot, weapon); } -} +} \ No newline at end of file diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 14dbc3e..5e62308 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -54,7 +54,9 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton() + .AddSingleton(); private static IServiceCollection AddData(this IServiceCollection services) => services.AddSingleton() @@ -66,6 +68,7 @@ public static class ServiceManager private static IServiceCollection AddInterop(this IServiceCollection services) => services.AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 012dde6..2fa50c7 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -12,13 +12,16 @@ namespace Glamourer.State; public class StateListener : IDisposable { - private readonly Configuration _config; - private readonly ActorService _actors; - private readonly StateManager _manager; - private readonly ItemManager _items; - private readonly PenumbraService _penumbra; - private readonly SlotUpdating _slotUpdating; - private readonly WeaponLoading _weaponLoading; + private readonly Configuration _config; + private readonly ActorService _actors; + private readonly StateManager _manager; + private readonly ItemManager _items; + private readonly PenumbraService _penumbra; + private readonly SlotUpdating _slotUpdating; + private readonly WeaponLoading _weaponLoading; + private readonly HeadGearVisibilityChanged _headGearVisibility; + private readonly VisorStateChanged _visorState; + private readonly WeaponVisibilityChanged _weaponVisibility; public bool Enabled { @@ -27,15 +30,19 @@ public class StateListener : IDisposable } 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; - _items = items; - _penumbra = penumbra; - _actors = actors; - _config = config; - _slotUpdating = slotUpdating; - _weaponLoading = weaponLoading; + _manager = manager; + _items = items; + _penumbra = penumbra; + _actors = actors; + _config = config; + _slotUpdating = slotUpdating; + _weaponLoading = weaponLoading; + _visorState = visorState; + _weaponVisibility = weaponVisibility; + _headGearVisibility = headGearVisibility; if (Enabled) Subscribe(); @@ -253,6 +260,9 @@ public class StateListener : IDisposable _penumbra.CreatingCharacterBase += OnCreatingCharacterBase; _slotUpdating.Subscribe(OnSlotUpdating, SlotUpdating.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() @@ -260,17 +270,19 @@ public class StateListener : IDisposable _penumbra.CreatingCharacterBase -= OnCreatingCharacterBase; _slotUpdating.Unsubscribe(OnSlotUpdating); _weaponLoading.Unsubscribe(OnWeaponLoading); + _visorState.Unsubscribe(OnVisorChange); + _headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange); + _weaponVisibility.Unsubscribe(OnWeaponVisibilityChange); } private UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterArmor armor) { var actorArmor = actor.GetArmor(slot); // 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) return UpdateState.Transformed; - // TODO: Hat State. - var baseData = state.BaseData.Armor(slot); var change = UpdateState.NoChange; if (baseData.Stain != armor.Stain) @@ -338,4 +350,68 @@ public class StateListener : IDisposable state.BaseData.Customize.Load(customize); return UpdateState.Change; } + + private void OnVisorChange(Model model, Ref 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 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 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(); + } + } } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index d3bb9f6..819541f 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -12,6 +12,7 @@ using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; using Glamourer.Structs; +using Lumina.Excel.GeneratedSheets; using OtterGui.Log; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; @@ -266,6 +267,14 @@ public class StateManager : IReadOnlyDictionary 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) @@ -335,6 +344,61 @@ public class StateManager : IReadOnlyDictionary _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)); + } + // ///// Change whether to apply a specific customize value. //public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)