diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index a288798..0a245b6 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -5,317 +5,304 @@ using Dalamud.Logging; using Dalamud.Plugin; using Dalamud.Plugin.Ipc; using System; +using Glamourer.Designs; +using Glamourer.State; +using Penumbra.Api.Enums; -namespace Glamourer.Api; +namespace Glamourer; -public class GlamourerIpc : IDisposable +public partial class Glamourer { - 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 ClientState _clientState; - private readonly ObjectTable _objectTable; - private readonly DalamudPluginInterface _pluginInterface; - - 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(ClientState clientState, ObjectTable objectTable, DalamudPluginInterface pluginInterface) + public class GlamourerIpc : IDisposable { - _clientState = clientState; - _objectTable = objectTable; - _pluginInterface = pluginInterface; + 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"; - InitializeProviders(); - } + private readonly ObjectTable _objectTable; + private readonly DalamudPluginInterface _pluginInterface; + private readonly Glamourer _glamourer; - public void Dispose() - => DisposeProviders(); + 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; - 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 + public GlamourerIpc(Glamourer glamourer, ClientState clientState, ObjectTable objectTable, + DalamudPluginInterface pluginInterface) { - 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}."); - //} - } + _glamourer = glamourer; + _objectTable = objectTable; + _pluginInterface = pluginInterface; - private static int GetApiVersion() - => CurrentApiVersion; - // - //private void ApplyAll(string customization, string characterName) - //{ - // var save = CharacterSave.FromString(customization); - // foreach (var gameObject in _objectTable) - // { - // if (gameObject.Name.ToString() != characterName) - // continue; - // - // var player = (Character)gameObject; - // Glamourer.RevertableDesigns.Revert(player); - // save.Apply(player); - // Glamourer.Penumbra.UpdateCharacters(player, null); - // break; - // } - //} - // - //private void ApplyAll(string customization, Character? character) - //{ - // if (character == null) - // return; - // var save = CharacterSave.FromString(customization); - // Glamourer.RevertableDesigns.Revert(character); - // save.Apply(character); - // Glamourer.Penumbra.UpdateCharacters(character, null); - //} - // - //private void ApplyOnlyCustomization(string customization, string characterName) - //{ - // var save = CharacterSave.FromString(customization); - // foreach (var gameObject in _objectTable) - // { - // if (gameObject.Name.ToString() != characterName) - // continue; - // - // var player = (Character)gameObject; - // Glamourer.RevertableDesigns.Revert(player); - // save.ApplyOnlyCustomizations(player); - // Glamourer.Penumbra.UpdateCharacters(player, null); - // break; - // } - //} - // - //private void ApplyOnlyCustomization(string customization, Character? character) - //{ - // if (character == null) - // return; - // var save = CharacterSave.FromString(customization); - // Glamourer.RevertableDesigns.Revert(character); - // save.ApplyOnlyCustomizations(character); - // Glamourer.Penumbra.UpdateCharacters(character, null); - //} - // - //private void ApplyOnlyEquipment(string customization, string characterName) - //{ - // var save = CharacterSave.FromString(customization); - // foreach (var gameObject in _objectTable) - // { - // if (gameObject.Name.ToString() != characterName) - // continue; - // - // var player = (Character)gameObject; - // Glamourer.RevertableDesigns.Revert(player); - // save.ApplyOnlyEquipment(player); - // Glamourer.Penumbra.UpdateCharacters(player, null); - // break; - // } - //} - // - //private void ApplyOnlyEquipment(string customization, Character? character) - //{ - // if (character == null) - // return; - // var save = CharacterSave.FromString(customization); - // Glamourer.RevertableDesigns.Revert(character); - // save.ApplyOnlyEquipment(character); - // Glamourer.Penumbra.UpdateCharacters(character, null); - //} - // - //private void Revert(string characterName) - //{ - // foreach (var gameObject in _objectTable) - // { - // if (gameObject.Name.ToString() != characterName) - // continue; - // - // var player = (Character)gameObject; - // Glamourer.RevertableDesigns.Revert(player); - // Glamourer.Penumbra.UpdateCharacters(player, null); - // return; - // } - // - // Glamourer.RevertableDesigns.RevertByNameWithoutApplication(characterName); - //} - // - //private void Revert(Character? character) - //{ - // if (character == null) - // return; - // Glamourer.RevertableDesigns.Revert(character); - // Glamourer.Penumbra.UpdateCharacters(character, null); - //} - // - //private string? GetAllCustomization(Character? character) - //{ - // if (character == null) - // return null; - // - // CharacterSave save = new CharacterSave(); - // save.LoadCharacter(character); - // return save.ToBase64(); - //} - // - //private string? GetAllCustomization(string characterName) - //{ - // CharacterSave save = null!; - // foreach (var gameObject in _objectTable) - // { - // if (gameObject.Name.ToString() != characterName) - // continue; - // - // var player = (Character)gameObject; - // save = new CharacterSave(); - // save.LoadCharacter(player); - // break; - // } - // - // return save?.ToBase64() ?? null; - //} + 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(customization, true, true); + var active = _glamourer._stateManager.GetOrCreateSave(character.Address); + _glamourer._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(customization, true, false); + var active = _glamourer._stateManager.GetOrCreateSave(character.Address); + _glamourer._stateManager.ApplyDesign(active, design, false); + } + + private void ApplyOnlyEquipment(string customization, string characterName) + { + foreach (var gameObject in _objectTable) + { + if (gameObject.Name.ToString() == characterName) + { + ApplyOnlyEquipment(customization, gameObject as Character); + return; + } + } + } + + private void ApplyOnlyEquipment(string customization, Character? character) + { + if (character == null) + return; + var design = Design.CreateTemporaryFromBase64(customization, false, true); + var active = _glamourer._stateManager.GetOrCreateSave(character.Address); + _glamourer._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.FromObject(character, true, false, false); + _glamourer._stateManager.DeleteSave(ident); + _glamourer._penumbra.RedrawObject(character.Address, RedrawType.Redraw); + } + + private string? GetAllCustomization(Character? character) + { + if (character == null) + return null; + + var ident = Actors.FromObject(character, true, false, false); + if (!_glamourer._stateManager.TryGetValue(ident, out var design)) + design = new ActiveDesign(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/Designs/Design.cs b/Glamourer/Designs/Design.cs index 4285997..e38babf 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -21,50 +21,50 @@ public partial class Design : DesignBase public string[] Tags { get; private set; } = Array.Empty(); public int Index { get; private set; } - private EquipFlag _applyEquip; - private CustomizeFlag _applyCustomize; - public QuadBool Wetness { get; private set; } = QuadBool.NullFalse; - public QuadBool Visor { get; private set; } = QuadBool.NullFalse; - public QuadBool Hat { get; private set; } = QuadBool.NullFalse; - public QuadBool Weapon { get; private set; } = QuadBool.NullFalse; - public bool WriteProtected { get; private set; } + public EquipFlag ApplyEquip { get; private set; } + public CustomizeFlag ApplyCustomize { get; private set; } + public QuadBool Wetness { get; private set; } = QuadBool.NullFalse; + public QuadBool Visor { get; private set; } = QuadBool.NullFalse; + public QuadBool Hat { get; private set; } = QuadBool.NullFalse; + public QuadBool Weapon { get; private set; } = QuadBool.NullFalse; + public bool WriteProtected { get; private set; } public bool DoApplyEquip(EquipSlot slot) - => _applyEquip.HasFlag(slot.ToFlag()); + => ApplyEquip.HasFlag(slot.ToFlag()); public bool DoApplyStain(EquipSlot slot) - => _applyEquip.HasFlag(slot.ToStainFlag()); + => ApplyEquip.HasFlag(slot.ToStainFlag()); public bool DoApplyCustomize(CustomizeIndex idx) - => _applyCustomize.HasFlag(idx.ToFlag()); + => ApplyCustomize.HasFlag(idx.ToFlag()); private bool SetApplyEquip(EquipSlot slot, bool value) { - var newValue = value ? _applyEquip | slot.ToFlag() : _applyEquip & ~slot.ToFlag(); - if (newValue == _applyEquip) + var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag(); + if (newValue == ApplyEquip) return false; - _applyEquip = newValue; + ApplyEquip = newValue; return true; } private bool SetApplyStain(EquipSlot slot, bool value) { - var newValue = value ? _applyEquip | slot.ToStainFlag() : _applyEquip & ~slot.ToStainFlag(); - if (newValue == _applyEquip) + var newValue = value ? ApplyEquip | slot.ToStainFlag() : ApplyEquip & ~slot.ToStainFlag(); + if (newValue == ApplyEquip) return false; - _applyEquip = newValue; + ApplyEquip = newValue; return true; } private bool SetApplyCustomize(CustomizeIndex idx, bool value) { - var newValue = value ? _applyCustomize | idx.ToFlag() : _applyCustomize & ~idx.ToFlag(); - if (newValue == _applyCustomize) + var newValue = value ? ApplyCustomize | idx.ToFlag() : ApplyCustomize & ~idx.ToFlag(); + if (newValue == ApplyCustomize) return false; - _applyCustomize = newValue; + ApplyCustomize = newValue; return true; } @@ -104,7 +104,8 @@ public partial class Design : DesignBase { [nameof(MainHand)] = Serialize(MainHand, CharacterData.MainHand.Stain, DoApplyEquip(EquipSlot.MainHand), DoApplyStain(EquipSlot.MainHand)), - [nameof(OffHand)] = Serialize(OffHand, CharacterData.OffHand.Stain, DoApplyEquip(EquipSlot.OffHand), DoApplyStain(EquipSlot.OffHand)), + [nameof(OffHand)] = Serialize(OffHand, CharacterData.OffHand.Stain, DoApplyEquip(EquipSlot.OffHand), + DoApplyStain(EquipSlot.OffHand)), }; foreach (var slot in EquipSlotExtensions.EqdpSlots) @@ -258,82 +259,41 @@ public partial class Design : DesignBase return false; } - public void MigrateBase64(string base64) { - static void CheckSize(int length, int requiredLength) - { - if (length != requiredLength) - throw new Exception( - $"Can not parse Base64 string into CharacterSave:\n\tInvalid size {length} instead of {requiredLength}."); - } - - var bytes = Convert.FromBase64String(base64); - - byte applicationFlags; - ushort equipFlags; - - switch (bytes[0]) - { - case 1: - { - CheckSize(bytes.Length, 86); - applicationFlags = bytes[1]; - equipFlags = BitConverter.ToUInt16(bytes, 2); - break; - } - case 2: - { - CheckSize(bytes.Length, 91); - applicationFlags = bytes[1]; - equipFlags = BitConverter.ToUInt16(bytes, 2); - Hat = Hat.SetValue((bytes[90] & 0x01) == 0); - Visor = Visor.SetValue((bytes[90] & 0x10) != 0); - Weapon = Weapon.SetValue((bytes[90] & 0x02) == 0); - break; - } - default: throw new Exception($"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]}."); - } - - _applyCustomize = (applicationFlags & 0x01) != 0 ? CustomizeFlagExtensions.All : 0; - Wetness = (applicationFlags & 0x02) != 0 ? QuadBool.True : QuadBool.NullFalse; - Hat = Hat.SetEnabled((applicationFlags & 0x04) != 0); - Weapon = Weapon.SetEnabled((applicationFlags & 0x08) != 0); - Visor = Visor.SetEnabled((applicationFlags & 0x10) != 0); - WriteProtected = (applicationFlags & 0x20) != 0; - - CharacterData.ModelId = 0; - - SetApplyEquip(EquipSlot.MainHand, (equipFlags & 0x0001) != 0); - SetApplyEquip(EquipSlot.OffHand, (equipFlags & 0x0002) != 0); - SetApplyStain(EquipSlot.MainHand, (equipFlags & 0x0001) != 0); - SetApplyStain(EquipSlot.OffHand, (equipFlags & 0x0002) != 0); - var flag = 0x0002u; + var data = MigrateBase64(base64, out var applyEquip, out var applyCustomize, out var writeProtected, out var wet, out var hat, + out var visor, out var weapon); + UpdateMainhand(data.MainHand); + UpdateMainhand(data.OffHand); foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - flag <<= 1; - var apply = (equipFlags & flag) != 0; - SetApplyEquip(slot, apply); - SetApplyStain(slot, apply); - } - unsafe - { - fixed (byte* ptr = bytes) - { - CharacterData.CustomizeData.Read(ptr + 4); - var cur = (CharacterWeapon*)(ptr + 30); - - UpdateMainhand(cur[0]); - SetStain(EquipSlot.MainHand, cur[0].Stain); - UpdateOffhand(cur[1]); - SetStain(EquipSlot.OffHand, cur[1].Stain); - var eq = (CharacterArmor*)(cur + 2); - foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) - { - UpdateArmor(slot, eq[idx], true); - SetStain(slot, eq[idx].Stain); - } - } - } + UpdateArmor(slot, data.Equipment[slot], true); + CharacterData.CustomizeData = data.CustomizeData; + ApplyEquip = applyEquip; + ApplyCustomize = applyCustomize; + WriteProtected = writeProtected; + Wetness = wet; + Hat = hat; + Visor = visor; + Weapon = weapon; } + + public static Design CreateTemporaryFromBase64(string base64, bool customize, bool equip) + { + var ret = new Design(); + ret.MigrateBase64(base64); + if (!customize) + ret.ApplyCustomize = 0; + if (!equip) + ret.ApplyEquip = 0; + ret.Wetness = ret.Wetness.SetEnabled(customize); + ret.Visor = ret.Visor.SetEnabled(equip); + ret.Hat = ret.Hat.SetEnabled(equip); + ret.Weapon = ret.Weapon.SetEnabled(equip); + return ret; + } + + // Outdated. + public string CreateOldBase64() + => CreateOldBase64(in CharacterData, ApplyEquip, ApplyCustomize, Wetness == QuadBool.True, Hat.ForcedValue, Hat.Enabled, + Visor.ForcedValue, Visor.Enabled, Weapon.ForcedValue, Weapon.Enabled, WriteProtected, 1f); } diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index e33a230..843f7cd 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -1,6 +1,8 @@ using System; using Glamourer.Customization; using Glamourer.Util; +using OtterGui.Classes; +using OtterGui; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -74,11 +76,12 @@ public class DesignBase }; } + public Weapon WeaponMain => new(MainhandName, MainHand, CharacterData.MainHand, MainhandType); public Weapon WeaponOff - => Designs.Weapon.Offhand(OffhandName, OffHand, CharacterData.OffHand, MainhandType); + => Weapon.Offhand(OffhandName, OffHand, CharacterData.OffHand, MainhandType); public CustomizeValue GetCustomize(CustomizeIndex idx) => Customize()[idx]; @@ -108,7 +111,6 @@ public class DesignBase protected bool UpdateArmor(EquipSlot slot, CharacterArmor armor, bool force) { if (!force) - { switch (slot) { case EquipSlot.Head when CharacterData.Head.Value == armor.Value: return false; @@ -122,7 +124,6 @@ public class DesignBase case EquipSlot.RFinger when CharacterData.RFinger.Value == armor.Value: return false; case EquipSlot.LFinger when CharacterData.LFinger.Value == armor.Value: return false; } - } var (valid, id, name) = Glamourer.Items.Identify(slot, armor.Set, armor.Variant); if (!valid) @@ -329,4 +330,123 @@ public class DesignBase default: return false; } } + + protected const int Base64Size = 89; + + public static CharacterData MigrateBase64(string base64, out EquipFlag equipFlags, out CustomizeFlag customizeFlags, + out bool writeProtected, out QuadBool wet, out QuadBool hat, out QuadBool visor, out QuadBool weapon) + { + static void CheckSize(int length, int requiredLength) + { + if (length != requiredLength) + throw new Exception( + $"Can not parse Base64 string into CharacterSave:\n\tInvalid size {length} instead of {requiredLength}."); + } + + byte applicationFlags; + ushort equipFlagsS; + var bytes = Convert.FromBase64String(base64); + hat = QuadBool.Null; + visor = QuadBool.Null; + weapon = QuadBool.Null; + switch (bytes[0]) + { + case 1: + { + CheckSize(bytes.Length, 86); + applicationFlags = bytes[1]; + equipFlagsS = BitConverter.ToUInt16(bytes, 2); + break; + } + case 2: + { + CheckSize(bytes.Length, Base64Size); + applicationFlags = bytes[1]; + equipFlagsS = BitConverter.ToUInt16(bytes, 2); + hat = hat.SetValue((bytes[90] & 0x01) == 0); + visor = visor.SetValue((bytes[90] & 0x10) != 0); + weapon = weapon.SetValue((bytes[90] & 0x02) == 0); + break; + } + default: throw new Exception($"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]}."); + } + + customizeFlags = (applicationFlags & 0x01) != 0 ? CustomizeFlagExtensions.All : 0; + wet = (applicationFlags & 0x02) != 0 ? QuadBool.True : QuadBool.NullFalse; + hat = hat.SetEnabled((applicationFlags & 0x04) != 0); + weapon = weapon.SetEnabled((applicationFlags & 0x08) != 0); + visor = visor.SetEnabled((applicationFlags & 0x10) != 0); + writeProtected = (applicationFlags & 0x20) != 0; + + equipFlags = 0; + equipFlags |= (equipFlagsS & 0x0001) != 0 ? EquipFlag.Mainhand | EquipFlag.MainhandStain : 0; + equipFlags |= (equipFlagsS & 0x0002) != 0 ? EquipFlag.Offhand | EquipFlag.OffhandStain : 0; + var flag = 0x0002u; + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + flag <<= 1; + equipFlags |= (equipFlagsS & flag) != 0 ? slot.ToFlag() | slot.ToStainFlag() : 0; + } + + var data = new CharacterData(); + unsafe + { + fixed (byte* ptr = bytes) + { + data.CustomizeData.Read(ptr + 4); + var cur = (CharacterWeapon*)(ptr + 30); + data.MainHand = cur[0]; + data.OffHand = cur[1]; + var eq = (CharacterArmor*)(cur + 2); + foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) + data.Equipment[slot] = eq[idx]; + } + } + + return data; + } + + public static unsafe string CreateOldBase64(in CharacterData save, EquipFlag equipFlags, CustomizeFlag customizeFlags, bool wet, bool hat, + bool setHat, bool visor, bool setVisor, bool weapon, bool setWeapon, bool writeProtected, float alpha) + { + var data = stackalloc byte[Base64Size]; + data[0] = 2; + data[1] = (byte)((customizeFlags == CustomizeFlagExtensions.All ? 0x01 : 0) + | (wet ? 0x02 : 0) + | (setHat ? 0x04 : 0) + | (setWeapon ? 0x08 : 0) + | (setVisor ? 0x10 : 0) + | (writeProtected ? 0x20 : 0)); + data[2] = (byte)((equipFlags.HasFlag(EquipFlag.Mainhand) ? 0x01 : 0) + | (equipFlags.HasFlag(EquipFlag.Offhand) ? 0x02 : 0) + | (equipFlags.HasFlag(EquipFlag.Head) ? 0x04 : 0) + | (equipFlags.HasFlag(EquipFlag.Body) ? 0x08 : 0) + | (equipFlags.HasFlag(EquipFlag.Hands) ? 0x10 : 0) + | (equipFlags.HasFlag(EquipFlag.Legs) ? 0x20 : 0) + | (equipFlags.HasFlag(EquipFlag.Feet) ? 0x40 : 0) + | (equipFlags.HasFlag(EquipFlag.Ears) ? 0x80 : 0)); + data[3] = (byte)((equipFlags.HasFlag(EquipFlag.Neck) ? 0x01 : 0) + | (equipFlags.HasFlag(EquipFlag.Wrist) ? 0x02 : 0) + | (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0) + | (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 0)); + save.CustomizeData.Write(data + 4); + ((CharacterWeapon*)(data + 30))[0] = save.MainHand; + ((CharacterWeapon*)(data + 30))[1] = save.OffHand; + ((CharacterArmor*)(data + 44))[0] = save.Head; + ((CharacterArmor*)(data + 44))[1] = save.Body; + ((CharacterArmor*)(data + 44))[2] = save.Hands; + ((CharacterArmor*)(data + 44))[3] = save.Legs; + ((CharacterArmor*)(data + 44))[4] = save.Feet; + ((CharacterArmor*)(data + 44))[5] = save.Ears; + ((CharacterArmor*)(data + 44))[6] = save.Neck; + ((CharacterArmor*)(data + 44))[7] = save.Wrists; + ((CharacterArmor*)(data + 44))[8] = save.RFinger; + ((CharacterArmor*)(data + 44))[9] = save.LFinger; + *(float*)(data + 84) = 1f; + data[88] = (byte)((hat ? 0x01 : 0) + | (visor ? 0x10 : 0) + | (weapon ? 0x02 : 0)); + + return Convert.ToBase64String(new Span(data, Base64Size)); + } } diff --git a/Glamourer/Designs/EquipFlag.cs b/Glamourer/Designs/EquipFlag.cs index e6a3a06..bbd700f 100644 --- a/Glamourer/Designs/EquipFlag.cs +++ b/Glamourer/Designs/EquipFlag.cs @@ -34,6 +34,8 @@ public enum EquipFlag : uint public static class EquipFlagExtensions { + public const EquipFlag All = (EquipFlag)(((uint)EquipFlag.OffhandStain << 1) - 1); + public static EquipFlag ToFlag(this EquipSlot slot) => slot switch { diff --git a/Glamourer/DrawObjectManager.cs b/Glamourer/DrawObjectManager.cs index 0ed68a5..2bcee68 100644 --- a/Glamourer/DrawObjectManager.cs +++ b/Glamourer/DrawObjectManager.cs @@ -36,7 +36,7 @@ public class DrawObjectManager : IDisposable private void FixEquipment(DrawObject drawObject, EquipSlot slot, ref CharacterArmor item) { var customize = drawObject.Customize; - var (changed, newArmor) = _items.RestrictedGear.ResolveRestricted(item, slot, customize.Race, customize.Gender); + var (changed, newArmor) = _items.ResolveRestrictedGear(item, slot, customize.Race, customize.Gender); if (!changed) return; @@ -54,7 +54,7 @@ public class DrawObjectManager : IDisposable if (gameObject.ModelId != modelId) return; - var identifier = _actors.FromObject((GameObject*)gameObjectPtr, out _, true, true); + var identifier = _actors.FromObject((GameObject*)gameObjectPtr, out _, true, true, false); if (!identifier.IsValid || !_manager.TryGetValue(identifier, out var design)) return; @@ -75,7 +75,7 @@ public class DrawObjectManager : IDisposable foreach (var slot in EquipSlotExtensions.EquipmentSlots) { (_, equip[slot]) = - Glamourer.Items.RestrictedGear.ResolveRestricted(saveEquip[slot], slot, customize.Race, customize.Gender); + Glamourer.Items.ResolveRestrictedGear(saveEquip[slot], slot, customize.Race, customize.Gender); } } } diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 03e539c..3f79bd5 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -4,7 +4,6 @@ using System.Reflection; using Dalamud.Game.Command; using Dalamud.Interface.Windowing; using Dalamud.Plugin; -using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Api; using Glamourer.Customization; using Glamourer.Designs; @@ -19,7 +18,7 @@ using FixedDesigns = Glamourer.State.FixedDesigns; namespace Glamourer; -public class Glamourer : IDalamudPlugin +public partial class Glamourer : IDalamudPlugin { private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],"; private const string MainCommandString = "/glamourer"; @@ -56,15 +55,14 @@ public class Glamourer : IDalamudPlugin //public readonly DesignManager Designs; //public static RevertableDesigns RevertableDesigns = new(); - //public readonly GlamourerIpc GlamourerIpc; + public readonly GlamourerIpc Ipc; public Glamourer(DalamudPluginInterface pluginInterface) { try { Dalamud.Initialize(pluginInterface); - Log = new Logger(); - + Log = new Logger(); _framework = new FrameworkManager(Dalamud.Framework, Log); _interop = new Interop.Interop(); _penumbra = new PenumbraAttach(); @@ -100,8 +98,8 @@ public class Glamourer : IDalamudPlugin _interface = new Interface(Dalamud.PluginInterface, Items, _stateManager, _designManager, _fileSystem, _objectManager); _windowSystem.AddWindow(_interface); - Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw; - //FixedDesignManager.Flag((Human*)((Actor)Dalamud.ClientState.LocalPlayer?.Address).Pointer->GameObject.DrawObject, 0, &x); + Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw; + Ipc = new GlamourerIpc(this, Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface); } catch { @@ -113,6 +111,7 @@ public class Glamourer : IDalamudPlugin public void Dispose() { + Ipc?.Dispose(); _drawObjectManager?.Dispose(); RedrawManager?.Dispose(); if (_windowSystem != null) diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index cd39d5f..e233d65 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Dalamud.Interface; -using Dalamud.Utility; using Glamourer.Designs; +using Glamourer.State; using Glamourer.Util; using ImGuiNET; using OtterGui; @@ -51,7 +51,7 @@ public class EquipmentDrawer if (slot.IsAccessory()) return gear.Name; - var (changed, _) = _items.RestrictedGear.ResolveRestricted(gear.Model, slot, race, gender); + var (changed, _) = _items.ResolveRestrictedGear(gear.Model, slot, race, gender); if (changed) return gear.Name + " (Restricted)"; @@ -136,26 +136,51 @@ public class EquipmentDrawer } public bool DrawApply(Design design, EquipSlot slot, out bool enabled) - { - enabled = design.DoApplyEquip(slot); - var tmp = enabled; - if (!ImGuiUtil.Checkbox($"##apply{slot}", string.Empty, enabled, v => tmp = v)) - return false; - - var change = enabled != tmp; - enabled = tmp; - return change; - } + => DrawCheckbox($"##apply{slot}", design.DoApplyEquip(slot), out enabled); public bool DrawApplyStain(Design design, EquipSlot slot, out bool enabled) - { - enabled = design.DoApplyStain(slot); - var tmp = enabled; - if (!ImGuiUtil.Checkbox($"##applyStain{slot}", string.Empty, enabled, v => tmp = v)) - return false; + => DrawCheckbox($"##applyStain{slot}", design.DoApplyStain(slot), out enabled); - var change = enabled != tmp; - enabled = tmp; - return change; + private static bool DrawCheckbox(string label, bool value, out bool on) + { + var ret = ImGuiUtil.Checkbox(label, string.Empty, value, v => value = v); + on = value; + return ret; } + + public bool DrawVisor(Design design, out bool on) + => DrawCheckbox("##visorToggled", design.Visor.ForcedValue, out on); + + public bool DrawVisor(ActiveDesign design, out bool on) + => DrawCheckbox("##visorToggled", design.IsVisorToggled, out on); + + public bool DrawHat(Design design, out bool on) + => DrawCheckbox("##hatVisible", design.Hat.ForcedValue, out on); + + public bool DrawHat(ActiveDesign design, out bool on) + => DrawCheckbox("##hatVisible", design.IsHatVisible, out on); + + public bool DrawWeapon(Design design, out bool on) + => DrawCheckbox("##weaponVisible", design.Weapon.ForcedValue, out on); + + public bool DrawWeapon(ActiveDesign design, out bool on) + => DrawCheckbox("##weaponVisible", design.IsWeaponVisible, out on); + + public bool DrawWetness(Design design, out bool on) + => DrawCheckbox("##wetness", design.Wetness.ForcedValue, out on); + + public bool DrawWetness(ActiveDesign design, out bool on) + => DrawCheckbox("##wetnessVisible", design.IsWet, out on); + + public bool DrawApplyVisor(Design design, out bool on) + => DrawCheckbox("##applyVisor", design.Visor.Enabled, out on); + + public bool DrawApplyWetness(Design design, out bool on) + => DrawCheckbox("##applyWetness", design.Wetness.Enabled, out on); + + public bool DrawApplyHatState(Design design, out bool on) + => DrawCheckbox("##applyHatState", design.Hat.Enabled, out on); + + public bool DrawApplyWeaponState(Design design, out bool on) + => DrawCheckbox("##applyWeaponState", design.Weapon.Enabled, out on); } diff --git a/Glamourer/Gui/Interface.Actors.cs b/Glamourer/Gui/Interface.Actors.cs index 5b2e1d4..7f4590c 100644 --- a/Glamourer/Gui/Interface.Actors.cs +++ b/Glamourer/Gui/Interface.Actors.cs @@ -64,7 +64,7 @@ internal partial class Interface return; if (_currentData.Valid) - _currentSave.Update(_currentData.Objects[0]); + _currentSave.Initialize(_currentData.Objects[0]); RevertButton(); if (_main._customizationDrawer.Draw(_currentSave.Customize(), _identifier.Type == IdentifierType.Special)) @@ -93,6 +93,11 @@ internal partial class Interface ImGui.SameLine(); _main._equipmentDrawer.DrawOffhand(currentOff, main.Type, out var off); } + + if (_main._equipmentDrawer.DrawVisor(_currentSave, out var value)) + { + _activeDesigns.ChangeVisor(_currentSave, value, false); + } } private const uint RedHeaderColor = 0xFF1818C0; diff --git a/Glamourer/Gui/Interface.SettingsTab.cs b/Glamourer/Gui/Interface.SettingsTab.cs index 544564c..b129985 100644 --- a/Glamourer/Gui/Interface.SettingsTab.cs +++ b/Glamourer/Gui/Interface.SettingsTab.cs @@ -43,19 +43,12 @@ internal partial class Interface private static void DrawRestorePenumbraButton() { const string buttonLabel = "Re-Register Penumbra"; - if (!Glamourer.Config.AttachToPenumbra) - { - using var style = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f); - ImGui.Button(buttonLabel); - return; - } - // TODO //if (ImGui.Button(buttonLabel)) // Glamourer.Penumbra.Reattach(true); - ImGuiUtil.HoverTooltip( - "If Penumbra did not register the functions for some reason, pressing this button might help restore functionality."); + //ImGuiUtil.HoverTooltip( + // "If Penumbra did not register the functions for some reason, pressing this button might help restore functionality."); } private static void DrawSettingsTab() diff --git a/Glamourer/Interop/Actor.cs b/Glamourer/Interop/Actor.cs index 2d9671e..0345264 100644 --- a/Glamourer/Interop/Actor.cs +++ b/Glamourer/Interop/Actor.cs @@ -25,7 +25,7 @@ public unsafe partial struct Actor : IEquatable, IDesignable => actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer; public ActorIdentifier GetIdentifier() - => Glamourer.Actors.FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Pointer, out _, true, true); + => Glamourer.Actors.FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Pointer, out _, true, true, false); public bool Identifier(out ActorIdentifier ident) { diff --git a/Glamourer/Interop/RedrawManager.Customize.cs b/Glamourer/Interop/RedrawManager.Customize.cs index 8a58333..68dcdd2 100644 --- a/Glamourer/Interop/RedrawManager.Customize.cs +++ b/Glamourer/Interop/RedrawManager.Customize.cs @@ -10,17 +10,5 @@ public unsafe partial class RedrawManager { - public static void SetVisor(Human* data, bool on) - { - if (data == null) - return; - var flags = &data->CharacterBase.UnkFlags_01; - var state = (*flags & Offsets.DrawObjectVisorStateFlag) != 0; - if (state == on) - return; - - var newFlag = (byte)(on ? *flags | Offsets.DrawObjectVisorStateFlag : *flags & ~Offsets.DrawObjectVisorStateFlag); - *flags = (byte) (newFlag | Offsets.DrawObjectVisorToggleFlag); - } } diff --git a/Glamourer/Interop/RedrawManager.cs b/Glamourer/Interop/RedrawManager.cs index 2f94af4..da955ae 100644 --- a/Glamourer/Interop/RedrawManager.cs +++ b/Glamourer/Interop/RedrawManager.cs @@ -20,11 +20,82 @@ public partial class Interop : IDisposable { SignatureHelper.Initialise(this); _changeJobHook.Enable(); + _flagSlotForUpdateHook.Enable(); + _setupVisorHook.Enable(); } public void Dispose() { _changeJobHook.Dispose(); + _flagSlotForUpdateHook.Dispose(); + _setupVisorHook.Dispose(); + } + + public static unsafe bool GetVisorState(nint humanPtr) + { + if (humanPtr == IntPtr.Zero) + return false; + + var data = (Human*)humanPtr; + var flags = &data->CharacterBase.UnkFlags_01; + return (*flags & Offsets.DrawObjectVisorStateFlag) != 0; + } + + public static unsafe void SetVisorState(nint humanPtr, bool on) + { + if (humanPtr == IntPtr.Zero) + return; + + var data = (Human*)humanPtr; + var flags = &data->CharacterBase.UnkFlags_01; + var state = (*flags & Offsets.DrawObjectVisorStateFlag) != 0; + if (state == on) + return; + + var newFlag = (byte)(on ? *flags | Offsets.DrawObjectVisorStateFlag : *flags & ~Offsets.DrawObjectVisorStateFlag); + *flags = (byte)(newFlag | Offsets.DrawObjectVisorToggleFlag); + } +} + +public partial class Interop +{ + private delegate void UpdateVisorDelegateInternal(nint humanPtr, ushort modelId, bool on); + public delegate void UpdateVisorDelegate(DrawObject human, SetId modelId, ref bool on); + + [Signature(Penumbra.GameData.Sigs.SetupVisor, DetourName = nameof(SetupVisorDetour))] + private readonly Hook _setupVisorHook = null!; + + public event UpdateVisorDelegate? VisorUpdate; + + private void SetupVisorDetour(nint humanPtr, ushort modelId, bool on) + { + InvokeVisorEvent(humanPtr, modelId, ref on); + _setupVisorHook.Original(humanPtr, modelId, on); + } + + private void InvokeVisorEvent(DrawObject drawObject, SetId modelId, ref bool on) + { + if (VisorUpdate == null) + { + Glamourer.Log.Verbose($"Visor setup on 0x{drawObject.Address:X} with {modelId.Value}, setting to {on}."); + return; + } + + var initialValue = on; + foreach (var del in VisorUpdate.GetInvocationList().OfType()) + { + try + { + del(drawObject, modelId, ref on); + } + catch (Exception ex) + { + Glamourer.Log.Error($"Could not invoke {nameof(VisorUpdate)} Subscriber:\n{ex}"); + } + } + + Glamourer.Log.Verbose( + $"Visor setup on 0x{drawObject.Address:X} with {modelId.Value}, setting to {on}, initial call was {initialValue}."); } } @@ -64,8 +135,13 @@ public unsafe partial class Interop private void InvokeFlagSlotEvent(DrawObject drawObject, EquipSlot slot, ref CharacterArmor armor) { if (EquipUpdate == null) + { + Glamourer.Log.Verbose( + $"{slot} updated on 0x{drawObject.Address:X} to {armor.Set.Value}-{armor.Variant} with stain {armor.Stain.Value}."); return; + } + var iv = armor; foreach (var del in EquipUpdate.GetInvocationList().OfType()) { try @@ -77,6 +153,9 @@ public unsafe partial class Interop Glamourer.Log.Error($"Could not invoke {nameof(EquipUpdate)} Subscriber:\n{ex}"); } } + + Glamourer.Log.Verbose( + $"{slot} updated on 0x{drawObject.Address:X} to {armor.Set.Value}-{armor.Variant} with stain {armor.Stain.Value}, initial armor was {iv.Set.Value}-{iv.Variant} with stain {iv.Stain.Value}."); } } @@ -122,8 +201,8 @@ public unsafe partial class RedrawManager : IDisposable public RedrawManager(FixedDesigns fixedDesigns, ActiveDesign.Manager stateManager) { SignatureHelper.Initialise(this); - _fixedDesigns = fixedDesigns; - _stateManager = stateManager; + _fixedDesigns = fixedDesigns; + _stateManager = stateManager; _flagSlotForUpdateHook.Enable(); _loadWeaponHook.Enable(); } @@ -161,7 +240,7 @@ public unsafe partial class RedrawManager : IDisposable foreach (var slot in EquipSlotExtensions.EqdpSlots) { (_, equip[slot]) = - Glamourer.Items.RestrictedGear.ResolveRestricted(saveEquip[slot], slot, customize.Race, customize.Gender); + Glamourer.Items.ResolveRestrictedGear(saveEquip[slot], slot, customize.Race, customize.Gender); } } } diff --git a/Glamourer/State/ActiveDesign.Manager.cs b/Glamourer/State/ActiveDesign.Manager.cs index 370788b..bc49ac4 100644 --- a/Glamourer/State/ActiveDesign.Manager.cs +++ b/Glamourer/State/ActiveDesign.Manager.cs @@ -4,7 +4,9 @@ using Glamourer.Interop; using Penumbra.GameData.Actors; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Api; using Glamourer.Customization; using Glamourer.Designs; @@ -65,20 +67,58 @@ public sealed partial class ActiveDesign public unsafe ActiveDesign GetOrCreateSave(Actor actor) { - var id = _actors.FromObject((GameObject*)actor.Pointer, out _, false, false); + var id = _actors.FromObject((GameObject*)actor.Pointer, out _, false, false, false); if (_characterSaves.TryGetValue(id, out var save)) { - save.Update(actor); + save.Initialize(actor); return save; } id = id.CreatePermanent(); save = new ActiveDesign(id, actor); - save.Update(actor); + save.Initialize(actor); _characterSaves.Add(id, save); return save; } + public void SetWetness(ActiveDesign design, bool wet, bool fromFixed) + => design.IsWet = wet; + + public void SetHatVisible(ActiveDesign design, bool visible, bool fromFixed) + => design.IsHatVisible = visible; + + public void SetVisor(ActiveDesign design, bool toggled, bool fromFixed) + => design.IsVisorToggled = toggled; + + public void SetWeaponVisible(ActiveDesign design, bool visible, bool fromFixed) + => design.IsWeaponVisible = visible; + + public unsafe void ApplyDesign(ActiveDesign to, Design from, bool fromFixed) + { + if (to.ModelId != from.ModelId) + return; + + if (from.DoApplyEquip(EquipSlot.MainHand)) + ChangeMainHand(to, from.MainHand, fromFixed); + + if (from.DoApplyEquip(EquipSlot.OffHand)) + ChangeOffHand(to, from.OffHand, fromFixed); + + foreach (var slot in EquipSlotExtensions.EqdpSlots.Where(from.DoApplyEquip)) + ChangeEquipment(to, slot, from.Armor(slot), fromFixed); + + ChangeCustomize(to, from.ApplyCustomize, *from.Customize().Data, fromFixed); + + if (from.Wetness.Enabled) + SetWetness(to, from.Wetness.ForcedValue, fromFixed); + if (from.Hat.Enabled) + SetHatVisible(to, from.Hat.ForcedValue, fromFixed); + if (from.Visor.Enabled) + SetVisor(to, from.Visor.ForcedValue, fromFixed); + if (from.Weapon.Enabled) + SetWeaponVisible(to, from.Weapon.ForcedValue, fromFixed); + } + public void RevertDesign(ActiveDesign design) { RevertCustomize(design, design.ChangedCustomize); @@ -89,6 +129,12 @@ public sealed partial class ActiveDesign RevertOffHand(design); } + public void ChangeMainHand(ActiveDesign design, uint itemId, bool fromFixed) + => design.SetMainhand(itemId); + + public void ChangeOffHand(ActiveDesign design, uint itemId, bool fromFixed) + => design.SetOffhand(itemId); + public void RevertMainHand(ActiveDesign design) { } @@ -207,5 +253,20 @@ public sealed partial class ActiveDesign foreach (var obj in data.Objects) _interop.UpdateStain(obj.DrawObject, slot, stain); } + + public void ChangeVisor(ActiveDesign design, bool on, bool fromFixed) + { + var current = design.IsVisorToggled; + if (current == on) + return; + + design.IsVisorToggled = on; + _objects.Update(); + if (!_objects.TryGetValue(design.Identifier, out var data)) + return; + + foreach (var obj in data.Objects) + Interop.Interop.SetVisorState(obj.DrawObject, on); + } } } diff --git a/Glamourer/State/ActiveDesign.cs b/Glamourer/State/ActiveDesign.cs index d26842a..b0764d5 100644 --- a/Glamourer/State/ActiveDesign.cs +++ b/Glamourer/State/ActiveDesign.cs @@ -1,4 +1,6 @@ -using Glamourer.Customization; +using System; +using System.Linq; +using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Interop; using Penumbra.Api.Enums; @@ -27,13 +29,12 @@ public sealed partial class ActiveDesign : DesignBase private ActiveDesign(ActorIdentifier identifier) => Identifier = identifier; - private ActiveDesign(ActorIdentifier identifier, Actor actor) + public ActiveDesign(ActorIdentifier identifier, Actor actor) { Identifier = identifier; - Update(actor); + Initialize(actor); } - //public void ApplyToActor(Actor actor) //{ // if (!actor) @@ -64,7 +65,7 @@ public sealed partial class ActiveDesign : DesignBase // RedrawManager.SetVisor(actor.DrawObject.Pointer, actor.VisorEnabled); //} // - public void Update(Actor actor) + public void Initialize(Actor actor) { if (!actor) return; @@ -101,5 +102,14 @@ public sealed partial class ActiveDesign : DesignBase UpdateOffhand(actor.OffHand); SetStain(EquipSlot.OffHand, actor.OffHand.Stain); } + + var visor = Interop.Interop.GetVisorState(actor.DrawObject); + if (IsVisorToggled != visor) + IsVisorToggled = visor; } + + public string CreateOldBase64() + => CreateOldBase64(in CharacterData, EquipFlagExtensions.All, CustomizeFlagExtensions.All, IsWet, IsHatVisible, true, + IsVisorToggled, + true, IsWeaponVisible, true, false, 1f); } diff --git a/Glamourer/State/GlamourerConfig.cs b/Glamourer/State/GlamourerConfig.cs index 2ea0fde..ceb7a84 100644 --- a/Glamourer/State/GlamourerConfig.cs +++ b/Glamourer/State/GlamourerConfig.cs @@ -1,47 +1,47 @@ using System.Collections.Generic; using Dalamud.Configuration; -namespace Glamourer.State +namespace Glamourer.State; + +public class GlamourerConfig : IPluginConfiguration { - public class GlamourerConfig : IPluginConfiguration + public class FixedDesign { - public class FixedDesign - { - public string Name = string.Empty; - public string Path = string.Empty; - public uint JobGroups; - public bool Enabled; - } + public string Name = string.Empty; + public string Path = string.Empty; + public uint JobGroups; + public bool Enabled; + } - public int Version { get; set; } = 1; + public int Version { get; set; } = 1; - public const uint DefaultCustomizationColor = 0xFFC000C0; - public const uint DefaultStateColor = 0xFF00C0C0; - public const uint DefaultEquipmentColor = 0xFF00C000; + public const uint DefaultCustomizationColor = 0xFFC000C0; + public const uint DefaultStateColor = 0xFF00C0C0; + public const uint DefaultEquipmentColor = 0xFF00C000; - public bool FoldersFirst { get; set; } = false; - public bool ColorDesigns { get; set; } = true; - public bool ShowLocks { get; set; } = true; - public bool AttachToPenumbra { get; set; } = true; - public bool ApplyFixedDesigns { get; set; } = true; + public bool UseRestrictedGearProtection { get; set; } = true; - public uint CustomizationColor { get; set; } = DefaultCustomizationColor; - public uint StateColor { get; set; } = DefaultStateColor; - public uint EquipmentColor { get; set; } = DefaultEquipmentColor; + public bool FoldersFirst { get; set; } = false; + public bool ColorDesigns { get; set; } = true; + public bool ShowLocks { get; set; } = true; + public bool ApplyFixedDesigns { get; set; } = true; - public List FixedDesigns { get; set; } = new(); + public uint CustomizationColor { get; set; } = DefaultCustomizationColor; + public uint StateColor { get; set; } = DefaultStateColor; + public uint EquipmentColor { get; set; } = DefaultEquipmentColor; - public void Save() - => Dalamud.PluginInterface.SavePluginConfig(this); + public List FixedDesigns { get; set; } = new(); - public static GlamourerConfig Load() - { - if (Dalamud.PluginInterface.GetPluginConfig() is GlamourerConfig config) - return config; + public void Save() + => Dalamud.PluginInterface.SavePluginConfig(this); - config = new GlamourerConfig(); - config.Save(); + public static GlamourerConfig Load() + { + if (Dalamud.PluginInterface.GetPluginConfig() is GlamourerConfig config) return config; - } + + config = new GlamourerConfig(); + config.Save(); + return config; } } diff --git a/Glamourer/Util/CustomizeExtensions.cs b/Glamourer/Util/CustomizeExtensions.cs index baae76b..7b32860 100644 --- a/Glamourer/Util/CustomizeExtensions.cs +++ b/Glamourer/Util/CustomizeExtensions.cs @@ -121,6 +121,6 @@ public static class CustomizeExtensions return; foreach (var slot in EquipSlotExtensions.EqdpSlots) - (_, equip[slot]) = Glamourer.Items.RestrictedGear.ResolveRestricted(equip[slot], slot, race, gender); + (_, equip[slot]) = Glamourer.Items.ResolveRestrictedGear(equip[slot], slot, race, gender); } } diff --git a/Glamourer/Util/ItemManager.cs b/Glamourer/Util/ItemManager.cs index 0d77ffa..001e870 100644 --- a/Glamourer/Util/ItemManager.cs +++ b/Glamourer/Util/ItemManager.cs @@ -10,6 +10,7 @@ using Penumbra.GameData; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using Race = Penumbra.GameData.Enums.Race; namespace Glamourer.Util; @@ -43,6 +44,14 @@ public class ItemManager : IDisposable RestrictedGear.Dispose(); } + public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender) + { + if (Glamourer.Config.UseRestrictedGearProtection) + return RestrictedGear.ResolveRestricted(armor, slot, race, gender); + + return (false, armor); + } + public readonly Item DefaultSword; public static uint NothingId(EquipSlot slot)