diff --git a/CustomizePlus/Api/CustomizePlusIpc.Profile.cs b/CustomizePlus/Api/CustomizePlusIpc.Profile.cs index bd25c64..c95c24f 100644 --- a/CustomizePlus/Api/CustomizePlusIpc.Profile.cs +++ b/CustomizePlus/Api/CustomizePlusIpc.Profile.cs @@ -15,6 +15,9 @@ using CustomizePlus.GameData.Extensions; using Dalamud.Game.ClientState.Objects.Types; using Penumbra.GameData.Structs; using Penumbra.GameData.Enums; +using CustomizePlus.Templates.Data; +using CustomizePlus.Templates.Events; +using Penumbra.GameData.Actors; namespace CustomizePlus.Api; @@ -144,7 +147,7 @@ public partial class CustomizePlusIpc if (actor == null || !actor.Value.Valid || !actor.Value.IsCharacter) return ((int)ErrorCode.InvalidCharacter, null); - var profile = _profileManager.GetProfileByActor(actor.Value, true); + var profile = _profileManager.GetActiveProfileByActor(actor.Value); if (profile == null) return ((int)ErrorCode.ProfileNotFound, null); @@ -268,6 +271,35 @@ public partial class CustomizePlusIpc } } + //Send profile update if any of the templates were changed in currently active profile + private void OnTemplateChanged(TemplateChanged.Type type, Template? template, object? arg3) + { + if (type != TemplateChanged.Type.EditorDisabled) + return; + + (ActorIdentifier actorIdentifier, bool hasChanges) = ((ActorIdentifier, bool))arg3; + + if (!hasChanges || actorIdentifier.Type != IdentifierType.Player) + return; + + var actor = _gameObjectService.GetLocalPlayerActor(); + if (!actor.Valid || !actorIdentifier.PlayerName.EqualsCi(actor.Utf8Name)) + return; + + var profile = _profileManager.GetActiveProfileByActor(actor); + if (profile == null) //safety check + return; + + if (!profile.Templates.Contains(template!)) + return; + + ICharacter? localPlayerCharacter = (ICharacter?)_gameObjectService.GetDalamudGameObjectFromActor(actor); + if (localPlayerCharacter == null) + return; + + OnProfileUpdateInternal(localPlayerCharacter, profile); + } + //warn: intended limitation - ignores default profiles because why you would use default profile on your own character private void OnArmatureChanged(ArmatureChanged.Type type, Armature armature, object? arg3) { diff --git a/CustomizePlus/Api/CustomizePlusIpc.cs b/CustomizePlus/Api/CustomizePlusIpc.cs index fab13a0..33e60cd 100644 --- a/CustomizePlus/Api/CustomizePlusIpc.cs +++ b/CustomizePlus/Api/CustomizePlusIpc.cs @@ -4,6 +4,8 @@ using CustomizePlus.Game.Services; using CustomizePlus.GameData.Services; using CustomizePlus.Profiles; using CustomizePlus.Profiles.Events; +using CustomizePlus.Templates.Data; +using CustomizePlus.Templates.Events; using Dalamud.Plugin; using ECommonsLite.EzIpcManager; using OtterGui.Log; @@ -29,6 +31,7 @@ public partial class CustomizePlusIpc : IDisposable private readonly CutsceneService _cutsceneService; private readonly ArmatureChanged _armatureChangedEvent; + private readonly TemplateChanged _templateChangedEvent; /// /// Shows if IPC failed to initialize or any other unrecoverable fatal error occured. @@ -43,7 +46,8 @@ public partial class CustomizePlusIpc : IDisposable GameObjectService gameObjectService, ProfileFileSystem profileFileSystem, CutsceneService cutsceneService, - ArmatureChanged armatureChangedEvent) + ArmatureChanged armatureChangedEvent, + TemplateChanged templateChangedEvent) { _pluginInterface = pluginInterface; _logger = logger; @@ -54,14 +58,17 @@ public partial class CustomizePlusIpc : IDisposable _cutsceneService = cutsceneService; _armatureChangedEvent = armatureChangedEvent; + _templateChangedEvent = templateChangedEvent; EzIPC.Init(this, "CustomizePlus"); _armatureChangedEvent.Subscribe(OnArmatureChanged, ArmatureChanged.Priority.CustomizePlusIpc); + _templateChangedEvent.Subscribe(OnTemplateChanged, TemplateChanged.Priority.CustomizePlusIpc); } public void Dispose() { _armatureChangedEvent.Unsubscribe(OnArmatureChanged); + _templateChangedEvent.Unsubscribe(OnTemplateChanged); } } diff --git a/CustomizePlus/Armatures/Services/ArmatureManager.cs b/CustomizePlus/Armatures/Services/ArmatureManager.cs index 4191623..18855e5 100644 --- a/CustomizePlus/Armatures/Services/ArmatureManager.cs +++ b/CustomizePlus/Armatures/Services/ArmatureManager.cs @@ -428,7 +428,15 @@ public unsafe sealed class ArmatureManager : IDisposable if (type == TemplateChanged.Type.EditorEnabled || type == TemplateChanged.Type.EditorDisabled) { - foreach (var armature in GetArmaturesForCharacter((ActorIdentifier)arg3!)) + ActorIdentifier actor; + bool hasChanges; + + if(type == TemplateChanged.Type.EditorEnabled) + actor = (ActorIdentifier)arg3; + else + (actor, hasChanges) = ((ActorIdentifier, bool))arg3; + + foreach (var armature in GetArmaturesForCharacter(actor)) { armature.IsPendingProfileRebind = true; _logger.Debug($"ArmatureManager.OnTemplateChange template editor enabled/disabled: {type}, pending profile set for {armature}"); diff --git a/CustomizePlus/Profiles/ProfileManager.cs b/CustomizePlus/Profiles/ProfileManager.cs index 2de39b4..f595ac4 100644 --- a/CustomizePlus/Profiles/ProfileManager.cs +++ b/CustomizePlus/Profiles/ProfileManager.cs @@ -432,7 +432,8 @@ public partial class ProfileManager : IDisposable /// /// Return profile by actor identifier, does not return temporary profiles. /// - public Profile? GetProfileByActor(Actor actor, bool enabledOnly = false) + /// todo: use GetEnabledProfilesByActor + public Profile? GetActiveProfileByActor(Actor actor) { var actorIdentifier = actor.GetIdentifier(_actorManager); @@ -442,14 +443,17 @@ public partial class ProfileManager : IDisposable if (actorIdentifier.Type == IdentifierType.Owned && !actorIdentifier.IsOwnedByLocalPlayer()) return null; - var query = Profiles.Where(p => p.Characters.Any(x => x.MatchesIgnoringOwnership(actorIdentifier)) && !p.IsTemporary); - if (enabledOnly) - query = query.Where(x => x.Enabled); + var query = Profiles.Where(p => p.Characters.Any(x => x.MatchesIgnoringOwnership(actorIdentifier)) && !p.IsTemporary && p.Enabled); var profile = query.OrderByDescending(x => x.Priority).FirstOrDefault(); - if (profile == null) + if(profile == null) + { + if (DefaultLocalPlayerProfile?.Enabled == true) + return DefaultLocalPlayerProfile; + return null; + } return profile; } diff --git a/CustomizePlus/Templates/TemplateEditorManager.cs b/CustomizePlus/Templates/TemplateEditorManager.cs index af9a2ca..580199c 100644 --- a/CustomizePlus/Templates/TemplateEditorManager.cs +++ b/CustomizePlus/Templates/TemplateEditorManager.cs @@ -9,6 +9,7 @@ using CustomizePlus.Profiles.Enums; using CustomizePlus.Templates.Data; using CustomizePlus.Templates.Events; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Classes; using OtterGui.Log; @@ -160,6 +161,10 @@ public class TemplateEditorManager : IDisposable _logger.Debug($"Disabling editor profile"); + //todo: can be optimized by storing actual reference to original template somewhere + var template = _templateManager.GetTemplate(CurrentlyEditedTemplateId); + var hasChanges = HasChanges; + CurrentlyEditedTemplateId = Guid.Empty; CurrentlyEditedTemplate = null; EditorProfile.Enabled = false; @@ -167,21 +172,35 @@ public class TemplateEditorManager : IDisposable IsEditorActive = false; HasChanges = false; - _event.Invoke(TemplateChanged.Type.EditorDisabled, null, Character); + _event.Invoke(TemplateChanged.Type.EditorDisabled, template, (Character, hasChanges)); return true; } - public void SaveChanges(bool asCopy = false) + public void SaveChangesAndDisableEditor(bool asCopy = false) { + if (!IsEditorActive || IsEditorPaused) + return; + + if(!HasChanges) + { + DisableEditor(); + return; + } + var targetTemplate = _templateManager.GetTemplate(CurrentlyEditedTemplateId); if (targetTemplate == null) throw new Exception($"Fatal editor error: Template with ID {CurrentlyEditedTemplateId} not found in template manager"); if (asCopy) + { targetTemplate = _templateManager.Clone(targetTemplate, $"{targetTemplate.Name} - Copy {Guid.NewGuid().ToString().Substring(0, 4)}", false); + HasChanges = false; //do this so EditorDisabled event sends proper info about the state of *currently edited* template + } _templateManager.ApplyBoneChangesAndSave(targetTemplate, CurrentlyEditedTemplate!); + + DisableEditor(); } public bool ChangeEditorCharacter(ActorIdentifier character) diff --git a/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs b/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs index 083d0f6..94f5819 100644 --- a/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs +++ b/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs @@ -341,8 +341,7 @@ public class BoneEditorPanel ImGui.SetCursorPos(new Vector2(xPos, yPos)); if (ImGui.Button("Save", buttonWidth)) { - _editorManager.SaveChanges(); - _editorManager.DisableEditor(); + _editorManager.SaveChangesAndDisableEditor(); ImGui.CloseCurrentPopup(); } @@ -350,8 +349,7 @@ public class BoneEditorPanel ImGui.SameLine(); if (ImGui.Button("Save as a copy", buttonWidth)) { - _editorManager.SaveChanges(true); - _editorManager.DisableEditor(); + _editorManager.SaveChangesAndDisableEditor(true); ImGui.CloseCurrentPopup(); }