Use actor identifiers in template editor

This commit is contained in:
RisaDev
2024-10-16 23:19:05 +03:00
parent 3781000d75
commit d6975591fe
9 changed files with 161 additions and 82 deletions

View File

@@ -368,9 +368,9 @@ public unsafe sealed class ArmatureManager : IDisposable
if (type == TemplateChanged.Type.EditorCharacterChanged)
{
(var characterName, var profile) = ((string, Profile))arg3;
(var character, var profile) = ((ActorIdentifier, Profile))arg3;
foreach (var armature in GetArmaturesForCharacterName(characterName))
foreach (var armature in GetArmaturesForCharacter(character))
{
armature.IsPendingProfileRebind = true;
_logger.Debug($"ArmatureManager.OnTemplateChange Editor profile character name changed, armature rebind scheduled: {type}, {armature}");
@@ -383,7 +383,7 @@ public unsafe sealed class ArmatureManager : IDisposable
foreach (var armature in profile.Armatures)
armature.IsPendingProfileRebind = true;
_logger.Debug($"ArmatureManager.OnTemplateChange Editor profile character name changed, armature rebind scheduled: {type}, profile: {profile.Name.Text.Incognify()}->{profile.Enabled}, new name: {characterName.Incognify()}");
_logger.Debug($"ArmatureManager.OnTemplateChange Editor profile character name changed, armature rebind scheduled: {type}, profile: {profile.Name.Text.Incognify()}->{profile.Enabled}, new name: {character.Incognito(null)}");
return;
}
@@ -391,7 +391,7 @@ public unsafe sealed class ArmatureManager : IDisposable
if (type == TemplateChanged.Type.EditorEnabled ||
type == TemplateChanged.Type.EditorDisabled)
{
foreach (var armature in GetArmaturesForCharacterName((string)arg3!))
foreach (var armature in GetArmaturesForCharacter((ActorIdentifier)arg3!))
{
armature.IsPendingProfileRebind = true;
_logger.Debug($"ArmatureManager.OnTemplateChange template editor enabled/disabled: {type}, pending profile set for {armature}");
@@ -516,17 +516,6 @@ public unsafe sealed class ArmatureManager : IDisposable
profile!.Armatures.ForEach(x => x.IsPendingProfileRebind = true);
}
private IEnumerable<Armature> GetArmaturesForCharacterName(string characterName)
{
foreach(var kvPair in Armatures)
{
(var actorIdentifier, _) = _gameObjectService.GetTrueActorForSpecialTypeActor(kvPair.Key);
if(actorIdentifier.ToNameWithoutOwnerName() == characterName)
yield return kvPair.Value;
}
}
private IEnumerable<Armature> GetArmaturesForCharacter(ActorIdentifier actorIdentifier)
{
foreach (var kvPair in Armatures)

View File

@@ -11,6 +11,8 @@ using CustomizePlus.Core.Data;
using CustomizePlus.Configuration.Services;
using CustomizePlus.UI.Windows;
using Dalamud.Interface.ImGuiNotification;
using Penumbra.GameData.Actors;
using CustomizePlus.Core.Helpers;
namespace CustomizePlus.Configuration.Data;
@@ -66,9 +68,8 @@ public class PluginConfiguration : IPluginConfiguration, ISavable
public bool ShowLiveBones { get; set; } = true;
public bool BoneMirroringEnabled { get; set; } = false;
public bool LimitLookupToOwnedObjects { get; set; } = false;
public string? PreviewCharacterName { get; set; } = null;
public ActorIdentifier PreviewCharacter { get; set; } = ActorIdentifier.Invalid;
public int EditorValuesPrecision { get; set; } = 3;
@@ -134,6 +135,7 @@ public class PluginConfiguration : IPluginConfiguration, ISavable
JsonConvert.PopulateObject(text, this, new JsonSerializerSettings
{
Error = HandleDeserializationError,
Converters = new List<JsonConverter> { new ActorIdentifierJsonConverter() }
});
}
catch (Exception ex)
@@ -153,6 +155,7 @@ public class PluginConfiguration : IPluginConfiguration, ISavable
{
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
serializer.Converters.Add(new ActorIdentifierJsonConverter());
serializer.Serialize(jWriter, this);
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Nodes;
using System.Text.Json;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Penumbra.GameData.Actors;
using Newtonsoft.Json.Linq;
namespace CustomizePlus.Core.Helpers;
internal sealed class ActorIdentifierJsonConverter : JsonConverter<ActorIdentifier>
{
public override ActorIdentifier ReadJson(JsonReader reader, Type objectType, ActorIdentifier existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
if (Penumbra.GameData.Actors.ActorIdentifierExtensions.Manager == null)
throw new Exception("Penumbra.GameData.Actors.ActorIdentifierExtensions.Manager is not ready");
return Penumbra.GameData.Actors.ActorIdentifierExtensions.Manager.FromJson(obj);
}
public override void WriteJson(JsonWriter writer, ActorIdentifier value, Newtonsoft.Json.JsonSerializer serializer)
{
value.ToJson().WriteTo(writer);
}
}

View File

@@ -46,8 +46,7 @@ public class SupportLogBuilderService
sb.Append($"> **`Commit Hash: `** {ThisAssembly.Git.Commit}+{ThisAssembly.Git.Sha}\n");
sb.Append($"> **`Plugin enabled: `** {_configuration.PluginEnabled}\n");
sb.AppendLine("**Settings -> Editor Settings**");
sb.Append($"> **`Limit to my creatures (editor): `** {_configuration.EditorConfiguration.LimitLookupToOwnedObjects}\n");
sb.Append($"> **`Preview character (editor): `** {_configuration.EditorConfiguration.PreviewCharacterName?.Incognify() ?? "Not set"}\n");
sb.Append($"> **`Preview character (editor): `** {_configuration.EditorConfiguration.PreviewCharacter.Incognito(null)}\n");
sb.Append($"> **`Set preview character on login: `** {_configuration.EditorConfiguration.SetPreviewToCurrentCharacterOnLogin}\n");
sb.Append($"> **`Root editing: `** {_configuration.EditorConfiguration.RootPositionEditingEnabled}\n");
sb.AppendLine("**Settings -> Profile application**");

View File

@@ -9,6 +9,7 @@ using ObjectManager = CustomizePlus.GameData.Services.ObjectManager;
using DalamudGameObject = Dalamud.Game.ClientState.Objects.Types.IGameObject;
using CustomizePlus.Configuration.Data;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Penumbra.GameData.Files.ShaderStructs;
namespace CustomizePlus.Game.Services;
@@ -59,8 +60,6 @@ public class GameObjectService
/// <summary>
/// Case sensitive
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public IEnumerable<(ActorIdentifier, Actor)> FindActorsByName(string name)
{
_objectManager.Update();
@@ -85,6 +84,36 @@ public class GameObjectService
}
}
/// <summary>
/// Searches using CompareIgnoringOwnership
/// </summary>
public IEnumerable<(ActorIdentifier, Actor)> FindActorsByIdentifier(ActorIdentifier identifier)
{
if (!identifier.IsValid)
yield break;
_objectManager.Update();
foreach (var kvPair in _objectManager.Identifiers)
{
var objectIdentifier = kvPair.Key;
(objectIdentifier, _) = GetTrueActorForSpecialTypeActor(objectIdentifier);
if (!objectIdentifier.IsValid)
continue;
if (identifier.CompareIgnoringOwnership(objectIdentifier))
{
if (kvPair.Value.Objects.Count > 1) //in gpose we can have more than a single object for one actor
foreach (var obj in kvPair.Value.Objects)
yield return (kvPair.Key.CreatePermanent(), obj);
else
yield return (kvPair.Key.CreatePermanent(), kvPair.Value.Objects[0]);
}
}
}
public Actor GetLocalPlayerActor()
{
_objectManager.Update();

View File

@@ -21,6 +21,7 @@ using CustomizePlus.Core.Extensions;
using CustomizePlus.Templates;
using CustomizePlus.Profiles;
using CustomizePlus.Armatures.Services;
using Penumbra.GameData.Actors;
namespace CustomizePlus;
@@ -44,6 +45,8 @@ public sealed class Plugin : IDalamudPlugin
_services = ServiceManagerBuilder.CreateProvider(pluginInterface, Logger);
_services.GetService<ActorManager>(); //needs to be initialized early for config to be read properly
//temporary
var v3ConfigFixer = _services.GetService<Version3ConfigFixer>();
v3ConfigFixer.FixV3ConfigIfNeeded();

View File

@@ -11,6 +11,7 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using OtterGui.Classes;
using OtterGui.Log;
using Penumbra.GameData.Actors;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -63,12 +64,22 @@ public class TemplateEditorManager : IDisposable
/// <summary>
/// Name of the preview character for the editor
/// </summary>
public string CharacterName => EditorProfile.CharacterName;
public ActorIdentifier Character => EditorProfile.Character;
/// <summary>
/// Checks if preview character exists at the time of call
/// </summary>
public bool IsCharacterFound => _gameObjectService.FindActorsByName(CharacterName).Count() > 0;
public bool IsCharacterFound
{
get
{
//todo: check with mounts/companions
var playerName = _gameObjectService.GetCurrentPlayerName();
return _gameObjectService.FindActorsByIdentifier(Character)
.Where(x => x.Item1.Type != Penumbra.GameData.Enums.IdentifierType.Owned || x.Item1.PlayerName.ToString() == playerName)
.Any();
}
}
public bool IsKeepOnlyEditorProfileActive { get; set; } //todo
@@ -95,7 +106,7 @@ public class TemplateEditorManager : IDisposable
Enabled = false,
Name = "Template editor profile",
ProfileType = ProfileType.Editor,
CharacterName = configuration.EditorConfiguration.PreviewCharacterName ?? LowerString.Empty
Character = configuration.EditorConfiguration.PreviewCharacter
};
}
@@ -112,7 +123,7 @@ public class TemplateEditorManager : IDisposable
if (IsEditorActive || IsEditorPaused)
return false;
_logger.Debug($"Enabling editor profile for {template.Name} via character {CharacterName}");
_logger.Debug($"Enabling editor profile for {template.Name} via character {Character.Incognito(null)}");
CurrentlyEditedTemplateId = template.UniqueId;
_currentlyEditedTemplateOriginal = template;
@@ -124,8 +135,8 @@ public class TemplateEditorManager : IDisposable
Name = "Template editor temporary template"
};
if (string.IsNullOrWhiteSpace(CharacterName)) //safeguard
ChangeEditorCharacterInternal(_gameObjectService.GetCurrentPlayerName()); //will set EditorProfile.CharacterName
if (!Character.IsValid) //safeguard
ChangeEditorCharacterInternal(_gameObjectService.GetCurrentPlayerActorIdentifier()); //will set EditorProfile.Character
EditorProfile.Templates.Clear(); //safeguard
EditorProfile.Templates.Add(CurrentlyEditedTemplate);
@@ -133,7 +144,7 @@ public class TemplateEditorManager : IDisposable
HasChanges = false;
IsEditorActive = true;
_event.Invoke(TemplateChanged.Type.EditorEnabled, template, CharacterName);
_event.Invoke(TemplateChanged.Type.EditorEnabled, template, Character);
return true;
}
@@ -155,7 +166,7 @@ public class TemplateEditorManager : IDisposable
IsEditorActive = false;
HasChanges = false;
_event.Invoke(TemplateChanged.Type.EditorDisabled, null, CharacterName);
_event.Invoke(TemplateChanged.Type.EditorDisabled, null, Character);
return true;
}
@@ -172,24 +183,24 @@ public class TemplateEditorManager : IDisposable
_templateManager.ApplyBoneChangesAndSave(targetTemplate, CurrentlyEditedTemplate!);
}
public bool ChangeEditorCharacter(string characterName)
public bool ChangeEditorCharacter(ActorIdentifier character)
{
if (!IsEditorActive || CharacterName == characterName || IsEditorPaused || string.IsNullOrWhiteSpace(characterName))
if (!IsEditorActive || Character == character || IsEditorPaused || !character.IsValid)
return false;
return ChangeEditorCharacterInternal(characterName);
return ChangeEditorCharacterInternal(character);
}
private bool ChangeEditorCharacterInternal(string characterName)
private bool ChangeEditorCharacterInternal(ActorIdentifier character)
{
_logger.Debug($"Changing character name for editor profile from {EditorProfile.CharacterName} to {characterName}");
_logger.Debug($"Changing character name for editor profile from {EditorProfile.Character.Incognito(null)} to {character.Incognito(null)}");
EditorProfile.CharacterName = characterName;
EditorProfile.Character = character;
_configuration.EditorConfiguration.PreviewCharacterName = CharacterName;
_configuration.EditorConfiguration.PreviewCharacter = character;
_configuration.Save();
_event.Invoke(TemplateChanged.Type.EditorCharacterChanged, CurrentlyEditedTemplate, (characterName, EditorProfile));
_event.Invoke(TemplateChanged.Type.EditorCharacterChanged, CurrentlyEditedTemplate, (character, EditorProfile));
return true;
}
@@ -294,19 +305,19 @@ public class TemplateEditorManager : IDisposable
private void OnLogin()
{
if (_configuration.EditorConfiguration.SetPreviewToCurrentCharacterOnLogin ||
string.IsNullOrWhiteSpace(_configuration.EditorConfiguration.PreviewCharacterName))
!_configuration.EditorConfiguration.PreviewCharacter.IsValid)
{
var localPlayerName = _gameObjectService.GetCurrentPlayerName();
if(string.IsNullOrWhiteSpace(localPlayerName))
var localPlayer = _gameObjectService.GetCurrentPlayerActorIdentifier();
if(!localPlayer.IsValid)
{
_logger.Warning("Can't retrieve local player name on login");
_logger.Warning("Can't retrieve local player on login");
return;
}
if (_configuration.EditorConfiguration.PreviewCharacterName != localPlayerName)
if (_configuration.EditorConfiguration.PreviewCharacter != localPlayer)
{
_logger.Debug("Resetting editor character because automatic condition triggered in OnLogin");
ChangeEditorCharacterInternal(localPlayerName);
ChangeEditorCharacterInternal(localPlayer);
}
}
}

View File

@@ -32,7 +32,6 @@ public class ProfilePanel
private readonly TemplateEditorEvent _templateEditorEvent;
private string? _newName;
//private string? _newCharacterName;
private Profile? _changedProfile;
private Action? _endAction;
@@ -219,7 +218,8 @@ public class ProfilePanel
bool showMultipleMessage = false;
if (_manager.DefaultProfile != _selector.Selected && !_selector.Selected!.ApplyToCurrentlyActiveCharacter)
{
ImGui.Text(_selector.Selected!.Character.IsValid ? $"Applies to {_selector.Selected?.Character.ToNameWithoutOwnerName()}" : "No valid character selected for the profile");
ImGui.Text(_selector.Selected!.Character.IsValid ? $"Applies to {(_selector.Selected?.Character.Type == Penumbra.GameData.Enums.IdentifierType.Owned ?
_selector.Selected?.Character.ToNameWithoutOwnerName() : _selector.Selected?.Character.ToString())}" : "No valid character selected for the profile");
ImGui.Text($"Legacy: {_selector.Selected!.CharacterName.Text ?? "None"}");
ImGui.Separator();

View File

@@ -15,6 +15,10 @@ using CustomizePlus.Core.Helpers;
using CustomizePlus.Templates;
using CustomizePlus.Game.Services;
using CustomizePlus.Templates.Data;
using CustomizePlus.UI.Windows.Controls;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using Penumbra.GameData.Actors;
using CustomizePlus.GameData.Extensions;
namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Templates;
@@ -24,6 +28,7 @@ public class BoneEditorPanel
private readonly TemplateEditorManager _editorManager;
private readonly PluginConfiguration _configuration;
private readonly GameObjectService _gameObjectService;
private readonly ActorAssignmentUi _actorAssignmentUi;
private BoneAttribute _editingAttribute;
private int _precision;
@@ -31,8 +36,6 @@ public class BoneEditorPanel
private bool _isShowLiveBones;
private bool _isMirrorModeEnabled;
private string? _newCharacterName;
private Dictionary<BoneData.BoneFamily, bool> _groupExpandedState = new();
private bool _openSavePopup;
@@ -48,12 +51,14 @@ public class BoneEditorPanel
TemplateFileSystemSelector templateFileSystemSelector,
TemplateEditorManager editorManager,
PluginConfiguration configuration,
GameObjectService gameObjectService)
GameObjectService gameObjectService,
ActorAssignmentUi actorAssignmentUi)
{
_templateFileSystemSelector = templateFileSystemSelector;
_editorManager = editorManager;
_configuration = configuration;
_gameObjectService = gameObjectService;
_actorAssignmentUi = actorAssignmentUi;
_isShowLiveBones = configuration.EditorConfiguration.ShowLiveBones;
_isMirrorModeEnabled = configuration.EditorConfiguration.BoneMirroringEnabled;
@@ -96,47 +101,57 @@ public class BoneEditorPanel
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)))
{
//using (var table = ImRaii.Table("BasicSettings", 2))
//{
/* ImGui.TableSetupColumn("BasicCol1", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Show editor preview on").X);
ImGui.TableSetupColumn("BasicCol2", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableNextRow();
var isShouldDraw = ImGui.CollapsingHeader("Preview settings");
ImGuiUtil.DrawFrameColumn("Show editor preview on");
ImGui.TableNextColumn();*/
if (isShouldDraw)
{
var width = new Vector2(ImGui.GetContentRegionAvail().X - ImGui.CalcTextSize("Limit to my creatures").X - 68, 0);
var name = _newCharacterName ?? _editorManager.CharacterName;
//ImGui.SetNextItemWidth(width.X);
var isShouldDraw = ImGui.CollapsingHeader("Preview settings");
if (isShouldDraw)
using (var disabled = ImRaii.Disabled(!IsEditorActive || IsEditorPaused))
{
using (var disabled = ImRaii.Disabled(!IsEditorActive || IsEditorPaused))
if (!_templateFileSystemSelector.IncognitoMode)
{
if (!_templateFileSystemSelector.IncognitoMode)
{
if (ImGui.InputText("##PreviewCharacterName", ref name, 128))
{
_newCharacterName = name;
}
ImGui.Text(_editorManager.Character.IsValid ? $"Applies to {(_editorManager.Character.Type == Penumbra.GameData.Enums.IdentifierType.Owned ?
_editorManager.Character.ToNameWithoutOwnerName() : _editorManager.Character.ToString())}" : "No valid character selected");
ImGui.Separator();
if (ImGui.IsItemDeactivatedAfterEdit())
{
if (string.IsNullOrWhiteSpace(_newCharacterName))
_newCharacterName = _gameObjectService.GetCurrentPlayerName();
_actorAssignmentUi.DrawWorldCombo(width.X / 2);
ImGui.SameLine();
_actorAssignmentUi.DrawPlayerInput(width.X / 2);
_editorManager.ChangeEditorCharacter(_newCharacterName);
var buttonWidth = new Vector2(165 * ImGuiHelpers.GlobalScale - ImGui.GetStyle().ItemSpacing.X / 2, 0);
_newCharacterName = null;
}
}
else
ImGui.TextUnformatted("Incognito active");
if (ImGuiUtil.DrawDisabledButton("Apply to player character", buttonWidth, string.Empty, !_actorAssignmentUi.CanSetPlayer))
_editorManager.ChangeEditorCharacter(_actorAssignmentUi.PlayerIdentifier);
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton("Apply to retainer", buttonWidth, string.Empty, !_actorAssignmentUi.CanSetRetainer))
_editorManager.ChangeEditorCharacter(_actorAssignmentUi.RetainerIdentifier);
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton("Apply to mannequin", buttonWidth, string.Empty, !_actorAssignmentUi.CanSetMannequin))
_editorManager.ChangeEditorCharacter(_actorAssignmentUi.MannequinIdentifier);
var currentPlayer = _gameObjectService.GetCurrentPlayerActorIdentifier();
if (ImGuiUtil.DrawDisabledButton("Apply to current character", buttonWidth, string.Empty, !currentPlayer.IsValid))
_editorManager.ChangeEditorCharacter(currentPlayer);
ImGui.Separator();
_actorAssignmentUi.DrawObjectKindCombo(width.X / 2);
ImGui.SameLine();
_actorAssignmentUi.DrawNpcInput(width.X / 2);
if (ImGuiUtil.DrawDisabledButton("Apply to selected NPC", buttonWidth, string.Empty, !_actorAssignmentUi.CanSetNpc))
_editorManager.ChangeEditorCharacter(_actorAssignmentUi.NpcIdentifier);
}
else
ImGui.TextUnformatted("Incognito active");
}
//}
}
ImGui.Separator();
using (var table = ImRaii.Table("BoneEditorMenu", 2))