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
{
private readonly (int Breaking, int Feature) _apiVersion = (5, 2);
private readonly (int Breaking, int Feature) _apiVersion = (6, 0);
/// <summary>
/// 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.GameData.Extensions;
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.Enums;
namespace CustomizePlus.Api;
@@ -46,7 +43,20 @@ public partial class CustomizePlusIpc
.Select(x =>
{
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();
}

View File

@@ -4,6 +4,8 @@ using CustomizePlus.Game.Services;
using CustomizePlus.GameData.Extensions;
using CustomizePlus.Profiles.Data;
using CustomizePlus.Templates.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -18,20 +20,24 @@ namespace CustomizePlus.Api.Data;
/// </summary>
public class IPCCharacterProfile
{
/// <summary>
/// Used only for display purposes
/// </summary>
public string CharacterName { get; set; } = "Invalid";
// public List<IPCCharacter> Characters { get; set; } = new();
public Dictionary<string, IPCBoneTransform> Bones { get; init; } = new();
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(),
Bones = new Dictionary<string, IPCBoneTransform>()
};
//todo: unify with tuple?
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)
{
@@ -53,7 +59,6 @@ public class IPCCharacterProfile
{
var fullProfile = new Profile
{
Name = $"IPC profile for {profile.CharacterName}",
//Character should be set manually
CreationDate = DateTimeOffset.UtcNow,
ModifiedDate = DateTimeOffset.UtcNow,
@@ -63,9 +68,11 @@ public class IPCCharacterProfile
ProfileType = isTemporary ? Profiles.Enums.ProfileType.Temporary : Profiles.Enums.ProfileType.Normal
};
fullProfile.Name = $"IPC Profile {fullProfile.UniqueId}";
var template = new Template
{
Name = $"{fullProfile.Name}'s template",
Name = $"{fullProfile.Name} - template",
CreationDate = fullProfile.CreationDate,
ModifiedDate = fullProfile.ModifiedDate,
UniqueId = Guid.NewGuid(),
@@ -137,3 +144,11 @@ public class IPCBoneTransform
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);
}
/// <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)
{
foreach (var kvPair in Armatures)
{
(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) &&
(armatureActorIdentifier.Type != IdentifierType.Owned || armatureActorIdentifier.IsOwnedByLocalPlayer()))
yield return kvPair.Value;

View File

@@ -373,6 +373,12 @@ public partial class ProfileManager : IDisposable
if (!actor.Identifier(_actorManager, out var identifier))
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.ProfileType = ProfileType.Temporary;
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.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)
{
_logger.Debug($"Temporary profile for {permanentIdentifier.Incognito(null)} already exists, removing...");
@@ -439,6 +445,9 @@ public partial class ProfileManager : IDisposable
if (!actorIdentifier.IsValid)
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);
if (enabledOnly)
query = query.Where(x => x.Enabled);
@@ -448,9 +457,6 @@ public partial class ProfileManager : IDisposable
if (profile == null)
return null;
if (actorIdentifier.Type == IdentifierType.Owned && !actorIdentifier.IsOwnedByLocalPlayer())
return null;
return profile;
}
@@ -480,8 +486,13 @@ public partial class ProfileManager : IDisposable
if (profile == DefaultLocalPlayerProfile)
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 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("\"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("Minions should now correctly synchronize via Mare Synchronos.", 1)
.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)
@@ -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)
.RegisterImportant("IPC notes, developers only.")
.RegisterEntry("IPC version is now 5.2.", 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)
.RegisterImportant("IPC version is now 6.0.", 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("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.")

View File

@@ -15,8 +15,6 @@ using ECommons.EzIpcManager;
using System;
using System.Collections;
using System.Collections.Generic;
using IPCProfileDataTuple = (System.Guid UniqueId, string Name, string VirtualPath, string CharacterName, bool IsEnabled);
using OtterGui.Log;
using CustomizePlus.Core.Extensions;
using CustomizePlus.Configuration.Data;
@@ -215,7 +213,9 @@ public class IPCTestTab //: IDisposable
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);
}