diff --git a/CustomizePlus.GameData/Extensions/ActorIdentifierExtensions.cs b/CustomizePlus.GameData/Extensions/ActorIdentifierExtensions.cs index 823819c..122d125 100644 --- a/CustomizePlus.GameData/Extensions/ActorIdentifierExtensions.cs +++ b/CustomizePlus.GameData/Extensions/ActorIdentifierExtensions.cs @@ -1,6 +1,7 @@ using Dalamud.Game.ClientState.Objects.Enums; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using PenumbraExtensions = Penumbra.GameData.Actors.ActorIdentifierExtensions; namespace CustomizePlus.GameData.Extensions; @@ -80,48 +81,4 @@ public static bool IsAllowedForProfiles(this ActorIdentifier identifier) return false; } } - - /// - /// Get "true" actor for special actors. Returns ActorIdentifier.Invalid for non-special actors or if actor cannot be found. - /// - /// - /// - /// - public static ActorIdentifier GetTrueActorForSpecialType(this ActorIdentifier identifier) - { - if (!identifier.IsValid) - return ActorIdentifier.Invalid; - - if (identifier.Type != IdentifierType.Special) - return ActorIdentifier.Invalid; - - if (PenumbraExtensions.Manager == null) - throw new Exception("ActorIdentifier.Manager is not initialized"); - - switch (identifier.Special) - { - case ScreenActor.GPosePlayer: - case ScreenActor.CharacterScreen: - case ScreenActor.FittingRoom: - case ScreenActor.DyePreview: - case ScreenActor.Portrait: - return PenumbraExtensions.Manager.GetCurrentPlayer(); - case ScreenActor.ExamineScreen: - var examineIdentifier = PenumbraExtensions.Manager.GetInspectPlayer(); - - if (!examineIdentifier.IsValid) - examineIdentifier = PenumbraExtensions.Manager.GetGlamourPlayer(); //returns ActorIdentifier.Invalid if player is invalid - - if (!examineIdentifier.IsValid) - return ActorIdentifier.Invalid; - - return examineIdentifier; - case ScreenActor.Card6: - case ScreenActor.Card7: - case ScreenActor.Card8: - return PenumbraExtensions.Manager.GetCardPlayer(); - } - - return ActorIdentifier.Invalid; - } } diff --git a/CustomizePlus/Armatures/Services/ArmatureManager.cs b/CustomizePlus/Armatures/Services/ArmatureManager.cs index 47eecfd..c37758f 100644 --- a/CustomizePlus/Armatures/Services/ArmatureManager.cs +++ b/CustomizePlus/Armatures/Services/ArmatureManager.cs @@ -99,6 +99,15 @@ public void OnGameObjectMove(Actor actor) ApplyRootTranslation(armature, actor); } + /// + /// Force profile rebind for all armatures + /// + public void RebindAllArmatures() + { + foreach (var kvPair in Armatures) + kvPair.Value.IsPendingProfileRebind = true; + } + /// /// Deletes armatures which no longer have actor associated with them and creates armatures for new actors /// @@ -542,9 +551,7 @@ private IEnumerable GetArmaturesForCharacterName(string characterName) { foreach(var kvPair in Armatures) { - var actorIdentifier = kvPair.Key; - if (actorIdentifier.Type == IdentifierType.Special) - actorIdentifier = actorIdentifier.GetTrueActorForSpecialType(); + (var actorIdentifier, _) = _gameObjectService.GetTrueActorForSpecialTypeActor(kvPair.Key); if(actorIdentifier.ToNameWithoutOwnerName() == characterName) yield return kvPair.Value; diff --git a/CustomizePlus/Configuration/Data/PluginConfiguration.cs b/CustomizePlus/Configuration/Data/PluginConfiguration.cs index b4e1bd7..961dcf1 100644 --- a/CustomizePlus/Configuration/Data/PluginConfiguration.cs +++ b/CustomizePlus/Configuration/Data/PluginConfiguration.cs @@ -86,6 +86,17 @@ public class CommandSettingsEntries public CommandSettingsEntries CommandSettings { get; set; } = new(); + [Serializable] + public class ProfileApplicationSettingsEntries + { + public bool ApplyInCharacterWindow { get; set; } = true; + public bool ApplyInTryOn { get; set; } = true; + public bool ApplyInCards { get; set; } = true; + public bool ApplyInInspect { get; set; } = true; + } + + public ProfileApplicationSettingsEntries ProfileApplicationSettings { get; set; } = new(); + [JsonIgnore] private readonly SaveService _saveService; diff --git a/CustomizePlus/Game/Services/GameObjectService.cs b/CustomizePlus/Game/Services/GameObjectService.cs index cd98820..d3177ce 100644 --- a/CustomizePlus/Game/Services/GameObjectService.cs +++ b/CustomizePlus/Game/Services/GameObjectService.cs @@ -7,6 +7,9 @@ using Penumbra.GameData.Interop; using ObjectManager = CustomizePlus.GameData.Services.ObjectManager; using DalamudGameObject = Dalamud.Game.ClientState.Objects.Types.GameObject; +using ECommons.Configuration; +using System; +using CustomizePlus.Configuration.Data; namespace CustomizePlus.Game.Services; @@ -15,12 +18,18 @@ public class GameObjectService private readonly ActorManager _actorManager; private readonly IObjectTable _objectTable; private readonly ObjectManager _objectManager; + private readonly PluginConfiguration _configuration; - public GameObjectService(ActorManager actorManager, IObjectTable objectTable, ObjectManager objectManager) + public GameObjectService( + ActorManager actorManager, + IObjectTable objectTable, + ObjectManager objectManager, + PluginConfiguration configuration) { _actorManager = actorManager; _objectTable = objectTable; _objectManager = objectManager; + _configuration = configuration; } public string GetCurrentPlayerName() @@ -54,8 +63,7 @@ public bool IsActorHasScalableRoot(Actor actor) { var identifier = kvPair.Key; - if (kvPair.Key.Type == IdentifierType.Special) - identifier = identifier.GetTrueActorForSpecialType(); + (identifier, _) = GetTrueActorForSpecialTypeActor(identifier); if (!identifier.IsValid) continue; @@ -80,4 +88,70 @@ public Actor GetLocalPlayerActor() { return _objectTable.CreateObjectReference(actor); } + + + /// + /// Get "true" actor for special actors. + /// This should be used everywhere where resolving proper actor is crucial for proper profile application + /// as identifiers returned by object manager with type "Special" need special handling. + /// + public (ActorIdentifier, SpecialResult) GetTrueActorForSpecialTypeActor(ActorIdentifier identifier) + { + if (identifier.Type != IdentifierType.Special) + return (identifier, SpecialResult.Invalid); + + if (_actorManager.ResolvePartyBannerPlayer(identifier.Special, out var id)) + return _configuration.ProfileApplicationSettings.ApplyInCards ? (id, SpecialResult.PartyBanner) : (identifier, SpecialResult.Invalid); + + if (_actorManager.ResolvePvPBannerPlayer(identifier.Special, out id)) + return _configuration.ProfileApplicationSettings.ApplyInCards ? (id, SpecialResult.PvPBanner) : (identifier, SpecialResult.Invalid); + + if (_actorManager.ResolveMahjongPlayer(identifier.Special, out id)) + return _configuration.ProfileApplicationSettings.ApplyInCards ? (id, SpecialResult.Mahjong) : (identifier, SpecialResult.Invalid); + + switch (identifier.Special) + { + case ScreenActor.GPosePlayer: + return (_actorManager.GetCurrentPlayer(), SpecialResult.GPosePlayer); + case ScreenActor.CharacterScreen when _configuration.ProfileApplicationSettings.ApplyInCharacterWindow: + return (_actorManager.GetCurrentPlayer(), SpecialResult.CharacterScreen); + case ScreenActor.FittingRoom when _configuration.ProfileApplicationSettings.ApplyInTryOn: + return (_actorManager.GetCurrentPlayer(), SpecialResult.FittingRoom); + case ScreenActor.DyePreview when _configuration.ProfileApplicationSettings.ApplyInTryOn: + return (_actorManager.GetCurrentPlayer(), SpecialResult.DyePreview); + case ScreenActor.Portrait when _configuration.ProfileApplicationSettings.ApplyInCards: + return (_actorManager.GetCurrentPlayer(), SpecialResult.Portrait); + case ScreenActor.ExamineScreen: + { + identifier = _actorManager.GetInspectPlayer(); + if (identifier.IsValid) + return (_configuration.ProfileApplicationSettings.ApplyInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Inspect); + + identifier = _actorManager.GetCardPlayer(); + if (identifier.IsValid) + return (_configuration.ProfileApplicationSettings.ApplyInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Card); + + return _configuration.ProfileApplicationSettings.ApplyInTryOn + ? (_actorManager.GetGlamourPlayer(), SpecialResult.Glamour) //returns ActorIdentifier.Invalid if player is invalid + : (identifier, SpecialResult.Invalid); + } + default: return (identifier, SpecialResult.Invalid); + } + } + + public enum SpecialResult + { + PartyBanner, + PvPBanner, + Mahjong, + CharacterScreen, + FittingRoom, + DyePreview, + Portrait, + Inspect, + Card, + Glamour, + GPosePlayer, + Invalid, + } } diff --git a/CustomizePlus/Profiles/ProfileManager.cs b/CustomizePlus/Profiles/ProfileManager.cs index 839c98e..d8a2cbf 100644 --- a/CustomizePlus/Profiles/ProfileManager.cs +++ b/CustomizePlus/Profiles/ProfileManager.cs @@ -26,6 +26,7 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using System.Runtime.Serialization; +using CustomizePlus.Game.Services; namespace CustomizePlus.Profiles; @@ -40,6 +41,7 @@ public class ProfileManager : IDisposable private readonly Logger _logger; private readonly PluginConfiguration _configuration; private readonly ActorManager _actorManager; + private readonly GameObjectService _gameObjectService; private readonly ProfileChanged _event; private readonly TemplateChanged _templateChangedEvent; private readonly ReloadEvent _reloadEvent; @@ -56,6 +58,7 @@ public ProfileManager( Logger logger, PluginConfiguration configuration, ActorManager actorManager, + GameObjectService gameObjectService, ProfileChanged @event, TemplateChanged templateChangedEvent, ReloadEvent reloadEvent, @@ -67,6 +70,7 @@ public ProfileManager( _logger = logger; _configuration = configuration; _actorManager = actorManager; + _gameObjectService = gameObjectService; _event = @event; _templateChangedEvent = templateChangedEvent; _templateChangedEvent.Subscribe(OnTemplateChange, TemplateChanged.Priority.ProfileManager); @@ -477,8 +481,7 @@ public IEnumerable GetEnabledProfilesByActor(ActorIdentifier actorIdent //performance: using textual override for ProfileAppliesTo here to not call //GetGameObjectName every time we are trying to check object against profiles - if (actorIdentifier.Type == IdentifierType.Special) - actorIdentifier = actorIdentifier.GetTrueActorForSpecialType(); + (actorIdentifier, _) = _gameObjectService.GetTrueActorForSpecialTypeActor(actorIdentifier); if (!actorIdentifier.IsValid) yield break; diff --git a/CustomizePlus/UI/Windows/MainWindow/Tabs/Debug/StateMonitoringTab.cs b/CustomizePlus/UI/Windows/MainWindow/Tabs/Debug/StateMonitoringTab.cs index eb36e40..75722a6 100644 --- a/CustomizePlus/UI/Windows/MainWindow/Tabs/Debug/StateMonitoringTab.cs +++ b/CustomizePlus/UI/Windows/MainWindow/Tabs/Debug/StateMonitoringTab.cs @@ -11,6 +11,7 @@ using CustomizePlus.GameData.Services; using CustomizePlus.Core.Extensions; using System.Numerics; +using CustomizePlus.Game.Services; namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Debug; @@ -20,17 +21,20 @@ public class StateMonitoringTab private readonly TemplateManager _templateManager; private readonly ArmatureManager _armatureManager; private readonly ObjectManager _objectManager; + private readonly GameObjectService _gameObjectService; public StateMonitoringTab( ProfileManager profileManager, TemplateManager templateManager, ArmatureManager armatureManager, - ObjectManager objectManager) + ObjectManager objectManager, + GameObjectService gameObjectService) { _profileManager = profileManager; _templateManager = templateManager; _armatureManager = armatureManager; _objectManager = objectManager; + _gameObjectService = gameObjectService; } public void Draw() @@ -106,8 +110,8 @@ private void DrawObjectManager() ImGui.Text($"Special: {kvPair.Key.Special.ToString()}"); ImGui.Text($"ToName: {kvPair.Key.ToName()}"); ImGui.Text($"ToNameWithoutOwnerName: {kvPair.Key.ToNameWithoutOwnerName()}"); - if(kvPair.Key.Type == Penumbra.GameData.Enums.IdentifierType.Special) - ImGui.Text($"True actor: {kvPair.Key.GetTrueActorForSpecialType().ToName()}"); + (var actorIdentifier, var specialResult) = _gameObjectService.GetTrueActorForSpecialTypeActor(kvPair.Key); + ImGui.Text($"True actor: {actorIdentifier.ToName()} ({specialResult})"); ImGui.Spacing(); ImGui.Spacing(); diff --git a/CustomizePlus/UI/Windows/MainWindow/Tabs/SettingsTab.cs b/CustomizePlus/UI/Windows/MainWindow/Tabs/SettingsTab.cs index fe66006..c6160c9 100644 --- a/CustomizePlus/UI/Windows/MainWindow/Tabs/SettingsTab.cs +++ b/CustomizePlus/UI/Windows/MainWindow/Tabs/SettingsTab.cs @@ -14,6 +14,7 @@ using CustomizePlus.Profiles; using CustomizePlus.Templates; using CustomizePlus.Core.Helpers; +using CustomizePlus.Armatures.Services; namespace CustomizePlus.UI.Windows.MainWindow.Tabs; @@ -22,29 +23,23 @@ public class SettingsTab private const uint DiscordColor = 0xFFDA8972; private readonly PluginConfiguration _configuration; - private readonly TemplateManager _templateManager; - private readonly ProfileManager _profileManager; + private readonly ArmatureManager _armatureManager; private readonly HookingService _hookingService; - private readonly SaveService _saveService; private readonly TemplateEditorManager _templateEditorManager; private readonly CPlusChangeLog _changeLog; private readonly MessageService _messageService; public SettingsTab( PluginConfiguration configuration, - TemplateManager templateManager, - ProfileManager profileManager, + ArmatureManager armatureManager, HookingService hookingService, - SaveService saveService, TemplateEditorManager templateEditorManager, CPlusChangeLog changeLog, MessageService messageService) { _configuration = configuration; - _templateManager = templateManager; - _profileManager = profileManager; + _armatureManager = armatureManager; _hookingService = hookingService; - _saveService = saveService; _templateEditorManager = templateEditorManager; _changeLog = changeLog; _messageService = messageService; @@ -63,6 +58,7 @@ public void Draw() using (var child2 = ImRaii.Child("SettingsChild")) { + DrawProfileApplicationSettings(); DrawInterface(); DrawCommands(); DrawAdvancedSettings(); @@ -96,6 +92,73 @@ private void DrawPluginEnabledCheckbox() } #endregion + #region Profile application settings + private void DrawProfileApplicationSettings() + { + var isShouldDraw = ImGui.CollapsingHeader("Profile Application"); + + if (!isShouldDraw) + return; + + DrawApplyInCharacterWindowCheckbox(); + DrawApplyInTryOnCheckbox(); + DrawApplyInCardsCheckbox(); + DrawApplyInInspectCheckbox(); + } + + private void DrawApplyInCharacterWindowCheckbox() + { + var isChecked = _configuration.ProfileApplicationSettings.ApplyInCharacterWindow; + + if (CtrlHelper.CheckboxWithTextAndHelp("##applyincharwindow", "Apply Profiles in Character Window", + "Apply profile for your character in your main character window, if it is set.", ref isChecked)) + { + _configuration.ProfileApplicationSettings.ApplyInCharacterWindow = isChecked; + _configuration.Save(); + _armatureManager.RebindAllArmatures(); + } + } + + private void DrawApplyInTryOnCheckbox() + { + var isChecked = _configuration.ProfileApplicationSettings.ApplyInTryOn; + + if (CtrlHelper.CheckboxWithTextAndHelp("##applyintryon", "Apply Profiles in Try-On Window", + "Apply profile for your character in your try-on, dye preview or glamour plate window, if it is set.", ref isChecked)) + { + _configuration.ProfileApplicationSettings.ApplyInTryOn = isChecked; + _configuration.Save(); + _armatureManager.RebindAllArmatures(); + } + } + + private void DrawApplyInCardsCheckbox() + { + var isChecked = _configuration.ProfileApplicationSettings.ApplyInCards; + + if (CtrlHelper.CheckboxWithTextAndHelp("##applyincards", "Apply Profiles in Adventurer Cards", + "Apply appropriate profile for the adventurer card you are currently looking at.", ref isChecked)) + { + _configuration.ProfileApplicationSettings.ApplyInCards = isChecked; + _configuration.Save(); + _armatureManager.RebindAllArmatures(); + } + } + + private void DrawApplyInInspectCheckbox() + { + var isChecked = _configuration.ProfileApplicationSettings.ApplyInInspect; + + if (CtrlHelper.CheckboxWithTextAndHelp("##applyininspect", "Apply Profiles in Inspect Window", + "Apply appropriate profile for the character you are currently inspecting.", ref isChecked)) + { + _configuration.ProfileApplicationSettings.ApplyInInspect = isChecked; + _configuration.Save(); + _armatureManager.RebindAllArmatures(); + } + } + #endregion + #region Chat Commands Settings private void DrawCommands() {