Added character info and priority to Profile.GetList IPC, bump of major IPC version, IPCCharacterProfile cleanup, some fixes which should hopefully fix temporary profiles for minions

This commit is contained in:
RisaDev
2024-10-22 21:03:50 +03:00
parent 17e09a8d26
commit 9061138cf1
8 changed files with 101 additions and 27 deletions

View File

@@ -4,7 +4,7 @@ namespace CustomizePlus.Api;
public partial class CustomizePlusIpc public partial class CustomizePlusIpc
{ {
private readonly (int Breaking, int Feature) _apiVersion = (5, 2); private readonly (int Breaking, int Feature) _apiVersion = (6, 0);
/// <summary> /// <summary>
/// When there are breaking changes the first number is bumped up and second one is reset. /// When there are breaking changes the first number is bumped up and second one is reset.

View File

@@ -13,11 +13,8 @@ using CustomizePlus.Armatures.Data;
using CustomizePlus.Armatures.Events; using CustomizePlus.Armatures.Events;
using CustomizePlus.GameData.Extensions; using CustomizePlus.GameData.Extensions;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Penumbra.GameData.Interop;
//Virtual path is full path to the profile in the virtual folders created by user in the profile list UI
using IPCProfileDataTuple = (System.Guid UniqueId, string Name, string VirtualPath, string CharacterName, bool IsEnabled);
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Penumbra.GameData.Enums;
namespace CustomizePlus.Api; namespace CustomizePlus.Api;
@@ -46,7 +43,20 @@ public partial class CustomizePlusIpc
.Select(x => .Select(x =>
{ {
string path = _profileFileSystem.FindLeaf(x, out var leaf) ? leaf.FullName() : x.Name.Text; string path = _profileFileSystem.FindLeaf(x, out var leaf) ? leaf.FullName() : x.Name.Text;
return (x.UniqueId, x.Name.Text, path, x.Characters.Count > 0 ? x.Characters[0].ToNameWithoutOwnerName() : "", x.Enabled); //todo: proper update to v5 var charactersList = new List<IPCCharacterDataTuple>(x.Characters.Count);
foreach (var character in x.Characters)
{
var tuple = new IPCCharacterDataTuple();
tuple.Name = character.ToNameWithoutOwnerName();
tuple.CharacterType = (byte)character.Type;
tuple.WorldId = character.Type == IdentifierType.Player || character.Type == IdentifierType.Owned ? character.HomeWorld.Id : WorldId.AnyWorld.Id;
tuple.CharacterSubType = character.Type == IdentifierType.Retainer ? (ushort)character.Retainer : (ushort)0;
charactersList.Add(tuple);
}
return (x.UniqueId, x.Name.Text, path, charactersList, x.Priority, x.Enabled);
}) })
.ToList(); .ToList();
} }

View File

@@ -4,6 +4,8 @@ using CustomizePlus.Game.Services;
using CustomizePlus.GameData.Extensions; using CustomizePlus.GameData.Extensions;
using CustomizePlus.Profiles.Data; using CustomizePlus.Profiles.Data;
using CustomizePlus.Templates.Data; using CustomizePlus.Templates.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -18,20 +20,24 @@ namespace CustomizePlus.Api.Data;
/// </summary> /// </summary>
public class IPCCharacterProfile public class IPCCharacterProfile
{ {
/// <summary> // public List<IPCCharacter> Characters { get; set; } = new();
/// Used only for display purposes
/// </summary>
public string CharacterName { get; set; } = "Invalid";
public Dictionary<string, IPCBoneTransform> Bones { get; init; } = new(); public Dictionary<string, IPCBoneTransform> Bones { get; init; } = new();
public static IPCCharacterProfile FromFullProfile(Profile profile) public static IPCCharacterProfile FromFullProfile(Profile profile)
{ {
var ipcProfile = new IPCCharacterProfile var ipcProfile = new IPCCharacterProfile();
/* foreach (var character in profile.Characters)
{ {
CharacterName = profile.Characters.FirstOrDefault().ToNameWithoutOwnerName(), //todo: unify with tuple?
Bones = new Dictionary<string, IPCBoneTransform>() var ipcCharacter = new IPCCharacter();
}; ipcCharacter.Name = character.ToNameWithoutOwnerName();
ipcCharacter.CharacterType = (byte)character.Type;
ipcCharacter.WorldId = character.Type == IdentifierType.Player || character.Type == IdentifierType.Owned ? character.HomeWorld.Id : WorldId.AnyWorld.Id;
ipcCharacter.CharacterSubType = character.Type == IdentifierType.Retainer ? (ushort)character.Retainer : (ushort)0;
ipcProfile.Characters.Add(ipcCharacter);
}*/
foreach (var template in profile.Templates) foreach (var template in profile.Templates)
{ {
@@ -53,7 +59,6 @@ public class IPCCharacterProfile
{ {
var fullProfile = new Profile var fullProfile = new Profile
{ {
Name = $"IPC profile for {profile.CharacterName}",
//Character should be set manually //Character should be set manually
CreationDate = DateTimeOffset.UtcNow, CreationDate = DateTimeOffset.UtcNow,
ModifiedDate = DateTimeOffset.UtcNow, ModifiedDate = DateTimeOffset.UtcNow,
@@ -63,9 +68,11 @@ public class IPCCharacterProfile
ProfileType = isTemporary ? Profiles.Enums.ProfileType.Temporary : Profiles.Enums.ProfileType.Normal ProfileType = isTemporary ? Profiles.Enums.ProfileType.Temporary : Profiles.Enums.ProfileType.Normal
}; };
fullProfile.Name = $"IPC Profile {fullProfile.UniqueId}";
var template = new Template var template = new Template
{ {
Name = $"{fullProfile.Name}'s template", Name = $"{fullProfile.Name} - template",
CreationDate = fullProfile.CreationDate, CreationDate = fullProfile.CreationDate,
ModifiedDate = fullProfile.ModifiedDate, ModifiedDate = fullProfile.ModifiedDate,
UniqueId = Guid.NewGuid(), UniqueId = Guid.NewGuid(),
@@ -137,3 +144,11 @@ public class IPCBoneTransform
return rotVec; return rotVec;
} }
} }
/*
public class IPCCharacter
{
public string Name { get; set; }
public ushort WorldId { get; set; }
public byte CharacterType { get; set; }
public ushort CharacterSubType { get; set; }
}*/

View File

@@ -0,0 +1,28 @@
global using IPCCharacterDataTuple = (string Name, ushort WorldId, byte CharacterType, ushort CharacterSubType);
//Virtual path is full path to the profile in the virtual folders created by user in the profile list UI
//Character.WorldId is value of Penumbra.GameData.Structs.WorldId. ushort.MaxValue if AnyWorld or if CharacterType != Player/Owned.
//Does not bear any meaning for CharacterType = Owned right now.
//CharacterType represents Penumbra.GameData.Enums.IdentifierType and can be one of the following:
//0 = Invalid (should never be returned in normal circumstances)
//1 = Player
//2 = Owned (companion, minion)
//3 = Unused
//4 = NPC
//5 = Retainer
//6 = Unused
//CharacterSubType represents Penumbra.GameData.Actors.ActorIdentifier.RetainerType and only used by CharacterType = Retainer and can be:
//0 = Both
//1 = Bell
//2 = Mannequin
global using IPCProfileDataTuple = (
System.Guid UniqueId,
string Name,
string VirtualPath,
System.Collections.Generic.List<(string Name, ushort WorldId, byte CharacterType, ushort CharacterSubType)> Characters,
int Priority,
bool IsEnabled);

View File

@@ -563,12 +563,19 @@ public unsafe sealed class ArmatureManager : IDisposable
profile!.Armatures.ForEach(x => x.IsPendingProfileRebind = true); profile!.Armatures.ForEach(x => x.IsPendingProfileRebind = true);
} }
/// <summary>
/// Warn: should not be used for temporary profiles as this limits search for Type = Owned to things owned by local player.
/// </summary>
private IEnumerable<Armature> GetArmaturesForCharacter(ActorIdentifier actorIdentifier) private IEnumerable<Armature> GetArmaturesForCharacter(ActorIdentifier actorIdentifier)
{ {
foreach (var kvPair in Armatures) foreach (var kvPair in Armatures)
{ {
(var armatureActorIdentifier, _) = _gameObjectService.GetTrueActorForSpecialTypeActor(kvPair.Key); (var armatureActorIdentifier, _) = _gameObjectService.GetTrueActorForSpecialTypeActor(kvPair.Key);
//warn: side-effect: for Type = Owned will ignore owner.
//This isn't a particularly huge issue as this is only used for profile rebinding, but this probably should be handled better later.
/*if (actorIdentifier.IsValid && armatureActorIdentifier.MatchesIgnoringOwnership(actorIdentifier))
yield return kvPair.Value;*/
if (actorIdentifier.IsValid && armatureActorIdentifier.MatchesIgnoringOwnership(actorIdentifier) && if (actorIdentifier.IsValid && armatureActorIdentifier.MatchesIgnoringOwnership(actorIdentifier) &&
(armatureActorIdentifier.Type != IdentifierType.Owned || armatureActorIdentifier.IsOwnedByLocalPlayer())) (armatureActorIdentifier.Type != IdentifierType.Owned || armatureActorIdentifier.IsOwnedByLocalPlayer()))
yield return kvPair.Value; yield return kvPair.Value;

View File

@@ -373,6 +373,12 @@ public partial class ProfileManager : IDisposable
if (!actor.Identifier(_actorManager, out var identifier)) if (!actor.Identifier(_actorManager, out var identifier))
throw new ActorNotFoundException(); throw new ActorNotFoundException();
/* if (identifier.Type != IdentifierType.Player)
{
_logger.Warning($"Tried applying temporary profile to actor {identifier.Incognito(null)}. Temporary profiles can only be applied to players right now.");
return; //do not return error code as I plan to eventually fix this
}*/
profile.Enabled = true; profile.Enabled = true;
profile.ProfileType = ProfileType.Temporary; profile.ProfileType = ProfileType.Temporary;
profile.Priority = int.MaxValue; //Make sure temporary profile is always at max priority profile.Priority = int.MaxValue; //Make sure temporary profile is always at max priority
@@ -381,7 +387,7 @@ public partial class ProfileManager : IDisposable
profile.Characters.Clear(); profile.Characters.Clear();
profile.Characters.Add(permanentIdentifier); //warn: identifier must not be AnyWorld or stuff will break! profile.Characters.Add(permanentIdentifier); //warn: identifier must not be AnyWorld or stuff will break!
var existingProfile = Profiles.FirstOrDefault(p => p.Characters.Count == 1 && p.Characters[0].MatchesIgnoringOwnership(permanentIdentifier) && p.IsTemporary); var existingProfile = Profiles.FirstOrDefault(p => p.Characters.Count == 1 && p.Characters[0].Matches(permanentIdentifier) && p.IsTemporary);
if (existingProfile != null) if (existingProfile != null)
{ {
_logger.Debug($"Temporary profile for {permanentIdentifier.Incognito(null)} already exists, removing..."); _logger.Debug($"Temporary profile for {permanentIdentifier.Incognito(null)} already exists, removing...");
@@ -439,6 +445,9 @@ public partial class ProfileManager : IDisposable
if (!actorIdentifier.IsValid) if (!actorIdentifier.IsValid)
return null; return null;
if (actorIdentifier.Type == IdentifierType.Owned && !actorIdentifier.IsOwnedByLocalPlayer())
return null;
var query = Profiles.Where(p => p.Characters.Any(x => x.MatchesIgnoringOwnership(actorIdentifier)) && !p.IsTemporary); var query = Profiles.Where(p => p.Characters.Any(x => x.MatchesIgnoringOwnership(actorIdentifier)) && !p.IsTemporary);
if (enabledOnly) if (enabledOnly)
query = query.Where(x => x.Enabled); query = query.Where(x => x.Enabled);
@@ -448,9 +457,6 @@ public partial class ProfileManager : IDisposable
if (profile == null) if (profile == null)
return null; return null;
if (actorIdentifier.Type == IdentifierType.Owned && !actorIdentifier.IsOwnedByLocalPlayer())
return null;
return profile; return profile;
} }
@@ -480,8 +486,13 @@ public partial class ProfileManager : IDisposable
if (profile == DefaultLocalPlayerProfile) if (profile == DefaultLocalPlayerProfile)
return false; return false;
if (actorIdentifier.Type == IdentifierType.Owned && !actorIdentifier.IsOwnedByLocalPlayer()) if (actorIdentifier.Type == IdentifierType.Owned)
{
if(profile.IsTemporary)
return profile.Characters.Any(x => x.Matches(actorIdentifier));
else if(!actorIdentifier.IsOwnedByLocalPlayer())
return false; return false;
}
return profile.Characters.Any(x => x.MatchesIgnoringOwnership(actorIdentifier)); return profile.Characters.Any(x => x.MatchesIgnoringOwnership(actorIdentifier));
} }

View File

@@ -49,6 +49,7 @@ public class CPlusChangeLog
.RegisterEntry("The way console commands work has not changed. This means that the commands will affect profiles the same way as before, even if profile affects multiple characters.", 3) .RegisterEntry("The way console commands work has not changed. This means that the commands will affect profiles the same way as before, even if profile affects multiple characters.", 3)
.RegisterEntry("\"Limit to my creatures\" option has been removed as it is now obsolete.", 2) .RegisterEntry("\"Limit to my creatures\" option has been removed as it is now obsolete.", 2)
.RegisterEntry("It is now possible to choose profile which will be applied to any character you login with.", 2) .RegisterEntry("It is now possible to choose profile which will be applied to any character you login with.", 2)
.RegisterEntry("Minions should now correctly synchronize via Mare Synchronos.", 1)
.RegisterHighlight("Added profile priority system.") .RegisterHighlight("Added profile priority system.")
.RegisterEntry("When several active profiles affect the same character, profile priority will be used to determine which profile will be applied to said character.", 1) .RegisterEntry("When several active profiles affect the same character, profile priority will be used to determine which profile will be applied to said character.", 1)
@@ -59,9 +60,11 @@ public class CPlusChangeLog
.RegisterEntry("Added option to configure if Customize+ main window will be automatically opened when you launch the game or not.", 1) .RegisterEntry("Added option to configure if Customize+ main window will be automatically opened when you launch the game or not.", 1)
.RegisterImportant("IPC notes, developers only.") .RegisterImportant("IPC notes, developers only.")
.RegisterEntry("IPC version is now 5.2.", 1) .RegisterImportant("IPC version is now 6.0.", 1)
.RegisterEntry("I did not want to bump major version for IPC and break other plugins, so all IPC endpoints are acting as if there is still only one character per profile. This is open for discussion over at support discord.", 1) .RegisterEntry("Profile.GetList has been updated to include profile priority as well as list of characters with their metadata. Please refer to Customize+ IPC source code files for additional information.", 1)
.RegisterEntry("Profile.OnUpdate event is now being triggered for profiles with \"Apply to all players and retainers\" and \"Apply to any character you are logged in with\" options enabled.", 1) .RegisterEntry("Profile.OnUpdate event is now being triggered for profiles with \"Apply to all players and retainers\" and \"Apply to any character you are logged in with\" options enabled.", 1)
.RegisterEntry("Format of the profile json expected by Profile.SetTemporaryProfileOnCharacter has been updated: CharacterName field removed.", 1)
.RegisterEntry("Temporary profiles should now apply correctly to non-player characters like minions.", 1)
.RegisterHighlight("Fixed issue when Customize+ did not detect changes in character skeleton. This mostly happened when altering character appearance via Glamourer and other plugins/tools.") .RegisterHighlight("Fixed issue when Customize+ did not detect changes in character skeleton. This mostly happened when altering character appearance via Glamourer and other plugins/tools.")

View File

@@ -15,8 +15,6 @@ using ECommons.EzIpcManager;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using IPCProfileDataTuple = (System.Guid UniqueId, string Name, string VirtualPath, string CharacterName, bool IsEnabled);
using OtterGui.Log; using OtterGui.Log;
using CustomizePlus.Core.Extensions; using CustomizePlus.Core.Extensions;
using CustomizePlus.Configuration.Data; using CustomizePlus.Configuration.Data;
@@ -215,7 +213,9 @@ public class IPCTestTab //: IDisposable
if (ImGui.Button("Copy user profile list to clipboard")) if (ImGui.Button("Copy user profile list to clipboard"))
{ {
ImGui.SetClipboardText(string.Join("\n", _getProfileListIpcFunc().Select(x => $"{x.UniqueId}, {x.Name}, {x.VirtualPath}, {x.CharacterName}, {x.IsEnabled}"))); ImGui.SetClipboardText(string.Join("\n",
_getProfileListIpcFunc().Select(x => $"{x.UniqueId}, {x.Name}, {x.VirtualPath}," +
$"|| {string.Join("|", x.Characters.Select(chr => $"{chr.Name}, {chr.WorldId}, {chr.CharacterType}, {chr.CharacterSubType}"))} ||, {x.Priority}, {x.IsEnabled}")));
_popupSystem.ShowPopup(PopupSystem.Messages.IPCCopiedToClipboard); _popupSystem.ShowPopup(PopupSystem.Messages.IPCCopiedToClipboard);
} }