diff --git a/CustomizePlus/Api/CustomizePlusIpc.General.cs b/CustomizePlus/Api/CustomizePlusIpc.General.cs index 7e027d0..50e386b 100644 --- a/CustomizePlus/Api/CustomizePlusIpc.General.cs +++ b/CustomizePlus/Api/CustomizePlusIpc.General.cs @@ -4,7 +4,7 @@ namespace CustomizePlus.Api; public partial class CustomizePlusIpc { - private readonly (int Breaking, int Feature) _apiVersion = (4, 0); + private readonly (int Breaking, int Feature) _apiVersion = (5, 0); /// /// When there are breaking changes the first number is bumped up and second one is reset. diff --git a/CustomizePlus/Api/CustomizePlusIpc.Profile.cs b/CustomizePlus/Api/CustomizePlusIpc.Profile.cs index 765e252..5cb1556 100644 --- a/CustomizePlus/Api/CustomizePlusIpc.Profile.cs +++ b/CustomizePlus/Api/CustomizePlusIpc.Profile.cs @@ -30,7 +30,7 @@ public partial class CustomizePlusIpc /// /!\ If no profile is set on specified character profile id will be equal to Guid.Empty /// [EzIPCEvent("Profile.OnUpdate")] - private Action OnProfileUpdate; + private Action OnProfileUpdate; /// /// Retrieve list of all user profiles @@ -125,12 +125,14 @@ private ErrorCode SetProfileStateInternal(Guid uniqueId, bool state) /// Get unique id of currently active profile for character. /// [EzIPC("Profile.GetActiveProfileIdOnCharacter")] - private (int, Guid?) GetActiveProfileIdOnCharacter(ICharacter character) + private (int, Guid?) GetActiveProfileIdOnCharacter(ushort gameObjectIndex) { - if (character == null) + var actor = _gameObjectService.GetActorByObjectIndex(gameObjectIndex); + + if (actor == null || !actor.Value.Valid || !actor.Value.IsCharacter) return ((int)ErrorCode.InvalidCharacter, null); - var profile = _profileManager.GetProfileByCharacterName(character.Name.ToString(), true); + var profile = _profileManager.GetProfileByCharacterName(actor.Value.Utf8Name.ToString(), true); if (profile == null) return ((int)ErrorCode.ProfileNotFound, null); @@ -143,14 +145,12 @@ private ErrorCode SetProfileStateInternal(Guid uniqueId, bool state) /// Returns profile's unique id which can be used to manipulate it at a later date. /// [EzIPC("Profile.SetTemporaryProfileOnCharacter")] - private (int, Guid?) SetTemporaryProfileOnCharacter(ICharacter character, string profileJson) + private (int, Guid?) SetTemporaryProfileOnCharacter(ushort gameObjectIndex, string profileJson) { - //todo: do not allow to set temporary profile on reserved actors (examine, etc) - if (character == null) - return ((int)ErrorCode.InvalidCharacter, null); + var actor = _gameObjectService.GetActorByObjectIndex(gameObjectIndex); - var actor = (Actor)character.Address; - if (!actor.Valid) + //todo: do not allow to set temporary profile on reserved actors (examine, etc) + if (actor == null || !actor.Value.Valid || !actor.Value.IsCharacter) return ((int)ErrorCode.InvalidCharacter, null); try @@ -162,26 +162,26 @@ private ErrorCode SetProfileStateInternal(Guid uniqueId, bool state) } catch (Exception ex) { - _logger.Error($"IPCCharacterProfile deserialization issue. Character: {character?.Name.ToString().Incognify()}, exception: {ex}."); + _logger.Error($"IPCCharacterProfile deserialization issue. Character: {actor.Value.Utf8Name.ToString().Incognify()}, exception: {ex}."); return ((int)ErrorCode.CorruptedProfile, null); } if (profile == null) { - _logger.Error($"IPCCharacterProfile is null after deserialization. Character: {character?.Name.ToString().Incognify()}."); + _logger.Error($"IPCCharacterProfile is null after deserialization. Character: {actor.Value.Utf8Name.ToString().Incognify()}."); return ((int)ErrorCode.CorruptedProfile, null); } //todo: ideally we'd probably want to make sure ID returned by that function does not have collision with other profiles var fullProfile = IPCCharacterProfile.ToFullProfile(profile).Item1; - _profileManager.AddTemporaryProfile(fullProfile, actor); + _profileManager.AddTemporaryProfile(fullProfile, actor.Value); return ((int)ErrorCode.Success, fullProfile.UniqueId); } catch (Exception ex) { - _logger.Error($"Unable to set temporary profile. Character: {character?.Name.ToString().Incognify()}, exception: {ex}."); + _logger.Error($"Unable to set temporary profile. Character: {actor.Value.Utf8Name.ToString().Incognify()}, exception: {ex}."); return ((int)ErrorCode.UnknownError, null); } } @@ -190,18 +190,16 @@ private ErrorCode SetProfileStateInternal(Guid uniqueId, bool state) /// Delete temporary profile currently active on character /// [EzIPC("Profile.DeleteTemporaryProfileOnCharacter")] - private int DeleteTemporaryProfileOnCharacter(ICharacter character) + private int DeleteTemporaryProfileOnCharacter(ushort gameObjectIndex) { - if (character == null) - return (int)ErrorCode.InvalidCharacter; + var actor = _gameObjectService.GetActorByObjectIndex(gameObjectIndex); - var actor = (Actor)character.Address; - if (!actor.Valid) + if (actor == null || !actor.Value.Valid || !actor.Value.IsCharacter) return (int)ErrorCode.InvalidCharacter; try { - _profileManager.RemoveTemporaryProfile(actor); + _profileManager.RemoveTemporaryProfile(actor.Value); return (int)ErrorCode.Success; } catch(ProfileException ex) @@ -213,13 +211,13 @@ private int DeleteTemporaryProfileOnCharacter(ICharacter character) case ProfileNotFoundException: return (int)ErrorCode.ProfileNotFound; default: - _logger.Error($"Exception in DeleteTemporaryProfileOnCharacter. Character: {character?.Name.ToString().Incognify()}. Exception: {ex}"); + _logger.Error($"Exception in DeleteTemporaryProfileOnCharacter. Character: {actor.Value.Utf8Name.ToString().Incognify()}. Exception: {ex}"); return (int)ErrorCode.UnknownError; } } catch(Exception ex) { - _logger.Error($"Exception in DeleteTemporaryProfileOnCharacter. Character: {character?.Name.ToString().Incognify()}. Exception: {ex}"); + _logger.Error($"Exception in DeleteTemporaryProfileOnCharacter. Character: {actor.Value.Utf8Name.ToString().Incognify()}. Exception: {ex}"); return (int)ErrorCode.UnknownError; } } @@ -309,7 +307,7 @@ private void OnArmatureChanged(ArmatureChanged.Type type, Armature armature, obj if (type == ArmatureChanged.Type.Deleted) { //Do not send event if default or editor profile was used - if (armature.Profile == _profileManager.DefaultProfile || armature.Profile.ProfileType == ProfileType.Editor) + if (armature.Profile == _profileManager.DefaultProfile || armature.Profile.ProfileType == ProfileType.Editor) //todo: never send if ProfileType != normal? return; OnProfileUpdateInternal(localPlayerCharacter, null); @@ -327,6 +325,6 @@ private void OnProfileUpdateInternal(ICharacter character, Profile? profile) _logger.Debug($"Sending player update message: Character: {character.Name.ToString().Incognify()}, Profile: {(profile != null ? profile.ToString() : "no profile")}"); - OnProfileUpdate(character, profile != null ? profile.UniqueId : Guid.Empty); + OnProfileUpdate(character.ObjectIndex, profile != null ? profile.UniqueId : Guid.Empty); } } diff --git a/CustomizePlus/Game/Services/GameObjectService.cs b/CustomizePlus/Game/Services/GameObjectService.cs index 8e15ba5..6fac33c 100644 --- a/CustomizePlus/Game/Services/GameObjectService.cs +++ b/CustomizePlus/Game/Services/GameObjectService.cs @@ -7,13 +7,8 @@ using Penumbra.GameData.Interop; using ObjectManager = CustomizePlus.GameData.Services.ObjectManager; using DalamudGameObject = Dalamud.Game.ClientState.Objects.Types.IGameObject; -using ECommons.Configuration; -using System; using CustomizePlus.Configuration.Data; -using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using Penumbra.GameData; -using Penumbra.String; -using Dalamud.Logging; +using FFXIVClientStructs.FFXIV.Client.Game.Object; namespace CustomizePlus.Game.Services; @@ -145,6 +140,19 @@ public Actor GetLocalPlayerActor() } } + /// + /// Find actor in object manager based on its Object ID. + /// + public Actor? GetActorByObjectIndex(ushort objectIndex) + { + if (objectIndex < 0 || objectIndex >= _objectManager.TotalCount) + return null; + + var ptr = _objectManager[(int)objectIndex]; + + return ptr; + } + public enum SpecialResult { PartyBanner, diff --git a/CustomizePlus/UI/Windows/MainWindow/Tabs/Debug/IPCTestTab.cs b/CustomizePlus/UI/Windows/MainWindow/Tabs/Debug/IPCTestTab.cs index ba0d248..6bcc7dd 100644 --- a/CustomizePlus/UI/Windows/MainWindow/Tabs/Debug/IPCTestTab.cs +++ b/CustomizePlus/UI/Windows/MainWindow/Tabs/Debug/IPCTestTab.cs @@ -50,13 +50,13 @@ public class IPCTestTab //: IDisposable private readonly Func _disableProfileByUniqueIdIpcFunc; [EzIPC("Profile.GetActiveProfileIdOnCharacter")] - private readonly Func _getActiveProfileIdOnCharacterIpcFunc; + private readonly Func _getActiveProfileIdOnCharacterIpcFunc; [EzIPC("Profile.SetTemporaryProfileOnCharacter")] - private readonly Func _setTemporaryProfileOnCharacterIpcFunc; + private readonly Func _setTemporaryProfileOnCharacterIpcFunc; [EzIPC("Profile.DeleteTemporaryProfileOnCharacter")] - private readonly Func _deleteTemporaryProfileOnCharacterIpcFunc; + private readonly Func _deleteTemporaryProfileOnCharacterIpcFunc; [EzIPC("Profile.DeleteTemporaryProfileByUniqueId")] private readonly Func _deleteTemporaryProfileByUniqueIdIpcFunc; @@ -64,11 +64,6 @@ public class IPCTestTab //: IDisposable [EzIPC("Profile.GetByUniqueId")] private readonly Func _getProfileByIdIpcFunc; - //private readonly ICallGateSubscriber? _setCharacterProfile; - //private readonly ICallGateSubscriber? _getProfileFromCharacter; - //private readonly ICallGateSubscriber? _revertCharacter; - //private readonly ICallGateSubscriber? _onProfileUpdate; - private string? _rememberedProfileJson; private (int, int) _apiVersion; @@ -103,24 +98,8 @@ public IPCTestTab( if (_getApiVersionIpcFunc != null) _apiVersion = _getApiVersionIpcFunc(); - - //_setCharacterProfile = pluginInterface.GetIpcSubscriber("CustomizePlus.SetProfileToCharacter"); - //_getProfileFromCharacter = pluginInterface.GetIpcSubscriber("CustomizePlus.GetProfileFromCharacter"); - //_revertCharacter = pluginInterface.GetIpcSubscriber("CustomizePlus.RevertCharacter"); - /*_onProfileUpdate = pluginInterface.GetIpcSubscriber("CustomizePlus.OnProfileUpdate"); - _onProfileUpdate.Subscribe(OnProfileUpdate);*/ } - /* public void Dispose() - { - _onProfileUpdate?.Unsubscribe(OnProfileUpdate); - } - - private void OnProfileUpdate(string? characterName, string? profileJson) - { - _lastProfileUpdate = DateTime.Now; - _lastProfileUpdateName = characterName; - } - */ + public unsafe void Draw() { _objectManager.Update(); @@ -141,7 +120,6 @@ public unsafe void Draw() ImGui.Separator(); - //ImGui.Text($"Last profile update: {_lastProfileUpdate}, Character: {_lastProfileUpdateName}"); ImGui.Text($"Memory: {(string.IsNullOrWhiteSpace(_rememberedProfileJson) ? "empty" : "has data")}"); ImGui.Text("Character to operate on:"); @@ -171,7 +149,7 @@ public unsafe void Draw() if (actors.Count == 0) return; - (int result, Guid? uniqueId) = _getActiveProfileIdOnCharacterIpcFunc(FindCharacterByAddress(actors[0].Item2.Address)); + (int result, Guid? uniqueId) = _getActiveProfileIdOnCharacterIpcFunc(actors[0].Item2.Index.Index); if(result == 0) { @@ -193,7 +171,7 @@ public unsafe void Draw() if (actors.Count == 0) return; - (int result, Guid? profileGuid) = _setTemporaryProfileOnCharacterIpcFunc(FindCharacterByAddress(actors[0].Item2.Address), _rememberedProfileJson); + (int result, Guid? profileGuid) = _setTemporaryProfileOnCharacterIpcFunc(actors[0].Item2.Index.Index, _rememberedProfileJson); if (result == 0) { _popupSystem.ShowPopup(PopupSystem.Messages.IPCSetProfileToChrDone); @@ -213,7 +191,7 @@ public unsafe void Draw() if (actors.Count == 0) return; - int result = _deleteTemporaryProfileOnCharacterIpcFunc(FindCharacterByAddress(actors[0].Item2.Address)); + int result = _deleteTemporaryProfileOnCharacterIpcFunc(actors[0].Item2.Index.Index); if (result == 0) _popupSystem.ShowPopup(PopupSystem.Messages.IPCRevertDone); else @@ -311,17 +289,10 @@ public unsafe void Draw() } [EzIPCEvent("Profile.OnUpdate")] - private void OnProfileUpdate(ICharacter Character, Guid ProfileUniqueId) - { - _logger.Debug($"IPC Test Tab - OnProfileUpdate: Character: {Character.Name.ToString().Incognify()}, Profile ID: {(ProfileUniqueId != Guid.Empty ? ProfileUniqueId.ToString() : "no id")}"); - } - - private ICharacter? FindCharacterByAddress(nint address) + private void OnProfileUpdate(ushort gameObjectIndex, Guid profileUniqueId) { - foreach (var obj in _objectTable) - if (obj.Address == address) - return (ICharacter)obj; + var actor = _gameObjectService.GetActorByObjectIndex(gameObjectIndex); - return null; + _logger.Debug($"IPC Test Tab - OnProfileUpdate: Character: {actor?.Utf8Name.ToString().Incognify() ?? "None"}, Profile ID: {(profileUniqueId != Guid.Empty ? profileUniqueId.ToString() : "no id")}"); } }