Added ability to apply profile to several characters
This commit is contained in:
@@ -85,6 +85,23 @@ public static class ActorIdentifierExtensions
|
||||
}
|
||||
}
|
||||
|
||||
public static string TypeToString(this ActorIdentifier identifier)
|
||||
{
|
||||
return identifier.Type switch
|
||||
{
|
||||
IdentifierType.Player => $" ({PenumbraExtensions.Manager?.Data.ToWorldName(identifier.HomeWorld) ?? "Unknown"})",
|
||||
IdentifierType.Retainer => $"{identifier.Retainer switch
|
||||
{
|
||||
ActorIdentifier.RetainerType.Bell => " (Bell)",
|
||||
ActorIdentifier.RetainerType.Mannequin => " (Mannequin)",
|
||||
_ => " (Retainer)",
|
||||
}}",
|
||||
IdentifierType.Owned => " (Companion/Mount)",
|
||||
IdentifierType.Npc => " (NPC)",
|
||||
_ => "",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For now used to determine if root scaling should be allowed or not
|
||||
/// </summary>
|
||||
|
||||
@@ -46,7 +46,7 @@ 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.Character.ToNameWithoutOwnerName(), x.Enabled); //todo: proper update to v5
|
||||
return (x.UniqueId, x.Name.Text, path, x.Characters[0].ToNameWithoutOwnerName(), x.Enabled); //todo: proper update to v5
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class IPCCharacterProfile
|
||||
{
|
||||
var ipcProfile = new IPCCharacterProfile
|
||||
{
|
||||
CharacterName = profile.Character.ToNameWithoutOwnerName(),
|
||||
CharacterName = profile.Characters.FirstOrDefault().ToNameWithoutOwnerName(),
|
||||
Bones = new Dictionary<string, IPCBoneTransform>()
|
||||
};
|
||||
|
||||
|
||||
@@ -411,7 +411,8 @@ public unsafe sealed class ArmatureManager : IDisposable
|
||||
type is not ProfileChanged.Type.Deleted &&
|
||||
type is not ProfileChanged.Type.TemporaryProfileAdded &&
|
||||
type is not ProfileChanged.Type.TemporaryProfileDeleted &&
|
||||
type is not ProfileChanged.Type.ChangedCharacter &&
|
||||
type is not ProfileChanged.Type.AddedCharacter &&
|
||||
type is not ProfileChanged.Type.RemovedCharacter &&
|
||||
type is not ProfileChanged.Type.ChangedDefaultProfile &&
|
||||
type is not ProfileChanged.Type.ChangedDefaultLocalPlayerProfile)
|
||||
return;
|
||||
@@ -456,23 +457,23 @@ public unsafe sealed class ArmatureManager : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
if (!profile.Character.IsValid)
|
||||
return;
|
||||
foreach(var character in profile.Characters)
|
||||
{
|
||||
if (!character.IsValid)
|
||||
continue;
|
||||
|
||||
foreach (var armature in GetArmaturesForCharacter(profile.Character))
|
||||
foreach (var armature in GetArmaturesForCharacter(character))
|
||||
{
|
||||
armature.IsPendingProfileRebind = true;
|
||||
_logger.Debug($"ArmatureManager.OnProfileChange profile {profile} toggled, planning rebind for armature {armature}");
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == ProfileChanged.Type.TemporaryProfileAdded)
|
||||
{
|
||||
if (!profile.Character.IsValid || !Armatures.ContainsKey(profile.Character)) //temporary profiles are never using AnyWorld identifiers so we should be fine here
|
||||
return;
|
||||
|
||||
//todo: remove this later
|
||||
/*Armature? armature = null;
|
||||
foreach(var kvPair in Armatures)
|
||||
@@ -489,7 +490,12 @@ public unsafe sealed class ArmatureManager : IDisposable
|
||||
if (armature == null)
|
||||
return;*/
|
||||
|
||||
var armature = Armatures[profile.Character];
|
||||
foreach(var character in profile.Characters)
|
||||
{
|
||||
if (!character.IsValid || !Armatures.ContainsKey(character))
|
||||
continue;
|
||||
|
||||
var armature = Armatures[character];
|
||||
|
||||
if (armature.Profile == profile)
|
||||
return;
|
||||
@@ -497,13 +503,15 @@ public unsafe sealed class ArmatureManager : IDisposable
|
||||
armature.UpdateLastSeen();
|
||||
|
||||
armature.IsPendingProfileRebind = true;
|
||||
}
|
||||
|
||||
_logger.Debug($"ArmatureManager.OnProfileChange TemporaryProfileAdded, calling rebind for existing armature: {type}, data payload: {arg3?.ToString()}, profile: {profile.Name.Text.Incognify()}->{profile.Enabled}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == ProfileChanged.Type.ChangedCharacter ||
|
||||
if (type == ProfileChanged.Type.AddedCharacter ||
|
||||
type == ProfileChanged.Type.RemovedCharacter ||
|
||||
type == ProfileChanged.Type.Deleted ||
|
||||
type == ProfileChanged.Type.TemporaryProfileDeleted)
|
||||
{
|
||||
@@ -518,7 +526,7 @@ public unsafe sealed class ArmatureManager : IDisposable
|
||||
armature.IsPendingProfileRebind = true;
|
||||
}
|
||||
|
||||
_logger.Debug($"ArmatureManager.OnProfileChange CC/DEL/TPD/ATCACC, armature rebind scheduled: {type}, data payload: {arg3?.ToString()?.Incognify()}, profile: {profile.Name.Text.Incognify()}->{profile.Enabled}");
|
||||
_logger.Debug($"ArmatureManager.OnProfileChange AC/RC/DEL/TPD/ATCACC, armature rebind scheduled: {type}, data payload: {arg3?.ToString()?.Incognify()}, profile: {profile.Name.Text.Incognify()}->{profile.Enabled}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,10 +183,10 @@ public class CommandService : IDisposable
|
||||
if (!isTurningOffAllProfiles)
|
||||
{
|
||||
profileName = subArgumentList[1].Trim();
|
||||
targetProfile = _profileManager.Profiles.FirstOrDefault(x => x.Name == profileName && x.Character.ToNameWithoutOwnerName() == characterName);
|
||||
targetProfile = _profileManager.Profiles.FirstOrDefault(x => x.Name == profileName && x.Characters.Any(x => x.ToNameWithoutOwnerName() == characterName));
|
||||
}
|
||||
else
|
||||
targetProfile = _profileManager.Profiles.FirstOrDefault(x => x.Character.ToNameWithoutOwnerName() == characterName && x.Enabled);
|
||||
targetProfile = _profileManager.Profiles.FirstOrDefault(x => x.Characters.Any(x => x.ToNameWithoutOwnerName() == characterName) && x.Enabled);
|
||||
|
||||
if (targetProfile == null)
|
||||
{
|
||||
@@ -225,7 +225,7 @@ public class CommandService : IDisposable
|
||||
.AddText(" was successfully ")
|
||||
.AddBlue(state != null ? ((bool)state ? "enabled" : "disabled") : "toggled")
|
||||
.AddText(" for ")
|
||||
.AddRed(targetProfile.Character.ToNameWithoutOwnerName()).BuiltString);
|
||||
.AddRed(string.Join(',', targetProfile.Characters.Select(x => x.ToNameWithoutOwnerName()))).BuiltString);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -68,7 +68,7 @@ public class SupportLogBuilderService
|
||||
sb.Append($"> > **`{profile.ToString(),-32}`*\n");
|
||||
sb.Append($"> > **`Name: `** {profile.Name.Text.Incognify()}\n");
|
||||
sb.Append($"> > **`Type: `** {profile.ProfileType} \n");
|
||||
sb.Append($"> > **`Character name: `** {profile.Character.Incognito(null)}\n");
|
||||
sb.Append($"> > **`Characters: `** {string.Join(',', profile.Characters.Select(x => x.Incognito(null)))}\n");
|
||||
sb.Append($"> > **`Templates:`**\n");
|
||||
sb.Append($"> > > **`Count: `** {profile.Templates.Count}\n");
|
||||
foreach (var template in profile.Templates)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using CustomizePlus.Armatures.Data;
|
||||
using CustomizePlus.Core.Data;
|
||||
using CustomizePlus.Core.Extensions;
|
||||
@@ -32,7 +33,8 @@ public sealed class Profile : ISavable
|
||||
/* [Obsolete("To be removed in the future versions")]
|
||||
public LowerString CharacterName { get; set; } = LowerString.Empty;*/
|
||||
|
||||
public ActorIdentifier Character { get; set; } = ActorIdentifier.Invalid;
|
||||
//public ActorIdentifier Character { get; set; } = ActorIdentifier.Invalid;
|
||||
public List<ActorIdentifier> Characters { get; set; } = new();
|
||||
|
||||
public LowerString Name { get; set; } = LowerString.Empty;
|
||||
|
||||
@@ -78,7 +80,7 @@ public sealed class Profile : ISavable
|
||||
/// <param name="original"></param>
|
||||
public Profile(Profile original) : this()
|
||||
{
|
||||
Character = original.Character;
|
||||
Characters = original.Characters.ToList();
|
||||
|
||||
foreach (var template in original.Templates)
|
||||
{
|
||||
@@ -88,7 +90,7 @@ public sealed class Profile : ISavable
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Profile '{Name.Text.Incognify()}' on {Character.Incognito(null)} [{UniqueId}]";
|
||||
return $"Profile '{Name.Text.Incognify()}' on {string.Join(',', Characters.Select(x => x.Incognito(null)))} [{UniqueId}]";
|
||||
}
|
||||
|
||||
#region Serialization
|
||||
@@ -101,7 +103,7 @@ public sealed class Profile : ISavable
|
||||
["UniqueId"] = UniqueId,
|
||||
["CreationDate"] = CreationDate,
|
||||
["ModifiedDate"] = ModifiedDate,
|
||||
["Character"] = Character.ToJson(),
|
||||
["Characters"] = SerializeCharacters(),
|
||||
["Name"] = Name.Text,
|
||||
["Enabled"] = Enabled,
|
||||
["IsWriteProtected"] = IsWriteProtected,
|
||||
@@ -124,6 +126,16 @@ public sealed class Profile : ISavable
|
||||
return ret;
|
||||
}
|
||||
|
||||
private JArray SerializeCharacters()
|
||||
{
|
||||
var ret = new JArray();
|
||||
foreach (var character in Characters)
|
||||
{
|
||||
ret.Add(character.ToJson());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
//Loading is in ProfileManager
|
||||
|
||||
@@ -15,7 +15,9 @@ public sealed class ProfileChanged() : EventWrapper<ProfileChanged.Type, Profile
|
||||
Deleted,
|
||||
Renamed,
|
||||
Toggled,
|
||||
ChangedCharacter,
|
||||
AddedCharacter,
|
||||
RemovedCharacter,
|
||||
//ChangedCharacter,
|
||||
AddedTemplate,
|
||||
RemovedTemplate,
|
||||
MovedTemplate,
|
||||
|
||||
@@ -101,30 +101,30 @@ public partial class ProfileManager : IDisposable
|
||||
var nameWordsCnt = characterName.Split(' ').Length;
|
||||
|
||||
if (_reverseNameDicts.TryGetID(ObjectKind.EventNpc, characterName, out var id))
|
||||
profile.Character = _actorManager.CreateNpc(ObjectKind.EventNpc, new NpcId(id));
|
||||
profile.Characters.Add(_actorManager.CreateNpc(ObjectKind.EventNpc, new NpcId(id)));
|
||||
else if (_reverseNameDicts.TryGetID(ObjectKind.BattleNpc, characterName, out id))
|
||||
profile.Character = _actorManager.CreateNpc(ObjectKind.BattleNpc, new NpcId(id));
|
||||
profile.Characters.Add(_actorManager.CreateNpc(ObjectKind.BattleNpc, new NpcId(id)));
|
||||
else if (_reverseNameDicts.TryGetID(ObjectKind.MountType, characterName, out id))
|
||||
{
|
||||
var currentPlayer = _actorManager.GetCurrentPlayer();
|
||||
profile.Character = _actorManager.CreateOwned(currentPlayer.PlayerName, currentPlayer.HomeWorld, ObjectKind.MountType, new NpcId(id));
|
||||
profile.Characters.Add(_actorManager.CreateOwned(currentPlayer.PlayerName, currentPlayer.HomeWorld, ObjectKind.MountType, new NpcId(id)));
|
||||
}
|
||||
else if (_reverseNameDicts.TryGetID(ObjectKind.Companion, characterName, out id))
|
||||
{
|
||||
var currentPlayer = _actorManager.GetCurrentPlayer();
|
||||
profile.Character = _actorManager.CreateOwned(currentPlayer.PlayerName, currentPlayer.HomeWorld, ObjectKind.Companion, new NpcId(id));
|
||||
profile.Characters.Add(_actorManager.CreateOwned(currentPlayer.PlayerName, currentPlayer.HomeWorld, ObjectKind.Companion, new NpcId(id)));
|
||||
}
|
||||
else if (nameWordsCnt == 2)
|
||||
profile.Character = _actorManager.CreatePlayer(ByteString.FromStringUnsafe(characterName, false), WorldId.AnyWorld);
|
||||
profile.Characters.Add(_actorManager.CreatePlayer(ByteString.FromStringUnsafe(characterName, false), WorldId.AnyWorld));
|
||||
else
|
||||
{
|
||||
_logger.Warning($"Unable to automatically migrate \"{profile.Name}\" to V5, unknown character name: {characterName}");
|
||||
_messageService.NotificationMessage($"Unable to detect character type for profile \"{profile.Name}\", please set character for this profile manually.", Dalamud.Interface.ImGuiNotification.NotificationType.Error);
|
||||
}
|
||||
|
||||
if (profile.Character.IsValid)
|
||||
if (profile.Characters.Count > 0)
|
||||
{
|
||||
_logger.Debug($"Upgraded profile \"{profile.Name}\" to V5: {characterName} -> {profile.Character}. Save queued.");
|
||||
_logger.Debug($"Upgraded profile \"{profile.Name}\" to V5: {characterName} -> {profile.Characters[0]}. Save queued.");
|
||||
_saveService.QueueSave(profile);
|
||||
}
|
||||
|
||||
@@ -135,14 +135,32 @@ public partial class ProfileManager : IDisposable
|
||||
{
|
||||
var profile = LoadProfileV4V5(obj);
|
||||
|
||||
var character = _actorManager.FromJson(obj["Character"] as JObject);
|
||||
if (obj["Characters"] is not JArray characterArray)
|
||||
return profile;
|
||||
|
||||
profile.Character = character;
|
||||
foreach(var characterObj in characterArray)
|
||||
{
|
||||
if (characterObj is not JObject characterObjCast)
|
||||
{
|
||||
//todo: warning
|
||||
continue;
|
||||
}
|
||||
|
||||
var character = _actorManager.FromJson(characterObjCast);
|
||||
|
||||
if(!character.IsValid)
|
||||
{
|
||||
//todo: warning
|
||||
continue;
|
||||
}
|
||||
|
||||
profile.Characters.Add(character);
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
//V4 and V5 are mostly not different, so common loading logic is here
|
||||
//V4 and V5 are mostly the same, so common loading logic is here
|
||||
private Profile LoadProfileV4V5(JObject obj)
|
||||
{
|
||||
var creationDate = obj["CreationDate"]?.ToObject<DateTimeOffset>() ?? throw new ArgumentNullException("CreationDate");
|
||||
|
||||
@@ -177,15 +177,14 @@ public partial class ProfileManager : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change character associated with profile
|
||||
/// Add character to profile
|
||||
/// </summary>
|
||||
public void ChangeCharacter(Profile profile, ActorIdentifier actorIdentifier)
|
||||
public void AddCharacter(Profile profile, ActorIdentifier actorIdentifier)
|
||||
{
|
||||
if (!actorIdentifier.IsValid || actorIdentifier.MatchesIgnoringOwnership(profile.Character))
|
||||
if (!actorIdentifier.IsValid || profile.Characters.Any(x => actorIdentifier.MatchesIgnoringOwnership(x)) || profile.IsTemporary)
|
||||
return;
|
||||
|
||||
var oldCharacter = profile.Character;
|
||||
profile.Character = actorIdentifier;
|
||||
profile.Characters.Add(actorIdentifier);
|
||||
|
||||
//Called so all other active profiles for new character name get disabled
|
||||
//saving is performed there
|
||||
@@ -193,8 +192,28 @@ public partial class ProfileManager : IDisposable
|
||||
|
||||
SaveProfile(profile);
|
||||
|
||||
_logger.Debug($"Changed character for profile {profile.UniqueId}.");
|
||||
_event.Invoke(ProfileChanged.Type.ChangedCharacter, profile, oldCharacter);
|
||||
_logger.Debug($"Add character for profile {profile.UniqueId}.");
|
||||
_event.Invoke(ProfileChanged.Type.AddedCharacter, profile, actorIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete character from profile
|
||||
/// </summary>
|
||||
public void DeleteCharacter(Profile profile, ActorIdentifier actorIdentifier)
|
||||
{
|
||||
if (!actorIdentifier.IsValid || !profile.Characters.Any(x => actorIdentifier.MatchesIgnoringOwnership(x)) || profile.IsTemporary)
|
||||
return;
|
||||
|
||||
profile.Characters.Remove(actorIdentifier);
|
||||
|
||||
//Called so all other active profiles for new character name get disabled
|
||||
//saving is performed there
|
||||
//SetEnabled(profile, profile.Enabled, true); //todo
|
||||
|
||||
SaveProfile(profile);
|
||||
|
||||
_logger.Debug($"Removed character from profile {profile.UniqueId}.");
|
||||
_event.Invoke(ProfileChanged.Type.RemovedCharacter, profile, actorIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -235,13 +254,36 @@ public partial class ProfileManager : IDisposable
|
||||
{
|
||||
_logger.Debug($"Setting {profile} as enabled...");
|
||||
|
||||
foreach (var otherProfile in Profiles
|
||||
.Where(x => x.Character.MatchesIgnoringOwnership(profile.Character) && x != profile && x.Enabled && !x.IsTemporary))
|
||||
foreach (var otherProfile in Profiles)
|
||||
{
|
||||
if (otherProfile == profile || !otherProfile.Enabled || otherProfile.IsTemporary)
|
||||
continue;
|
||||
|
||||
bool shouldDisable = false;
|
||||
|
||||
//my god this is ugly
|
||||
foreach(var otherCharacter in otherProfile.Characters)
|
||||
{
|
||||
foreach(var currentCharacter in profile.Characters)
|
||||
{
|
||||
if(otherCharacter.MatchesIgnoringOwnership(currentCharacter))
|
||||
{
|
||||
shouldDisable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDisable)
|
||||
break;
|
||||
}
|
||||
|
||||
if(shouldDisable)
|
||||
{
|
||||
_logger.Debug($"\t-> {otherProfile} disabled");
|
||||
SetEnabled(otherProfile, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oldValue != value)
|
||||
{
|
||||
@@ -366,12 +408,15 @@ public partial class ProfileManager : IDisposable
|
||||
|
||||
profile.Enabled = true;
|
||||
profile.ProfileType = ProfileType.Temporary;
|
||||
profile.Character = identifier.CreatePermanent(); //warn: identifier must not be AnyWorld or stuff will break!
|
||||
|
||||
var existingProfile = Profiles.FirstOrDefault(x => x.Character.MatchesIgnoringOwnership(profile.Character) && x.IsTemporary);
|
||||
var permanentIdentifier = identifier.CreatePermanent();
|
||||
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);
|
||||
if (existingProfile != null)
|
||||
{
|
||||
_logger.Debug($"Temporary profile for {existingProfile.Character.Incognito(null)} already exists, removing...");
|
||||
_logger.Debug($"Temporary profile for {permanentIdentifier.Incognito(null)} already exists, removing...");
|
||||
Profiles.Remove(existingProfile);
|
||||
_event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, existingProfile, null);
|
||||
}
|
||||
@@ -381,16 +426,19 @@ public partial class ProfileManager : IDisposable
|
||||
//Make sure temporary profiles come first, so they are returned by all other methods first
|
||||
Profiles.Sort((x, y) => y.IsTemporary.CompareTo(x.IsTemporary));
|
||||
|
||||
_logger.Debug($"Added temporary profile for {profile.Character}");
|
||||
_logger.Debug($"Added temporary profile for {permanentIdentifier}");
|
||||
_event.Invoke(ProfileChanged.Type.TemporaryProfileAdded, profile, null);
|
||||
}
|
||||
|
||||
public void RemoveTemporaryProfile(Profile profile)
|
||||
{
|
||||
if (!profile.IsTemporary)
|
||||
return;
|
||||
|
||||
if (!Profiles.Remove(profile))
|
||||
throw new ProfileNotFoundException();
|
||||
|
||||
_logger.Debug($"Removed temporary profile for {profile.Character.Incognito(null)}");
|
||||
_logger.Debug($"Removed temporary profile for {profile.Characters[0].Incognito(null)}");
|
||||
|
||||
_event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, profile, null);
|
||||
}
|
||||
@@ -409,7 +457,7 @@ public partial class ProfileManager : IDisposable
|
||||
if (!actor.Identifier(_actorManager, out var identifier))
|
||||
throw new ActorNotFoundException();
|
||||
|
||||
var profile = Profiles.FirstOrDefault(x => x.Character == identifier && x.IsTemporary);
|
||||
var profile = Profiles.FirstOrDefault(x => x.Characters[0] == identifier && x.IsTemporary);
|
||||
if (profile == null)
|
||||
throw new ProfileNotFoundException();
|
||||
|
||||
@@ -426,7 +474,7 @@ public partial class ProfileManager : IDisposable
|
||||
if (!actorIdentifier.IsValid)
|
||||
return null;
|
||||
|
||||
var query = Profiles.Where(x => x.Character.MatchesIgnoringOwnership(actorIdentifier) && !x.IsTemporary);
|
||||
var query = Profiles.Where(p => p.Characters.Any(x => x.MatchesIgnoringOwnership(actorIdentifier)) && !p.IsTemporary);
|
||||
if (enabledOnly)
|
||||
query = query.Where(x => x.Enabled);
|
||||
|
||||
@@ -475,7 +523,7 @@ public partial class ProfileManager : IDisposable
|
||||
if (actorIdentifier.Type == IdentifierType.Owned && !actorIdentifier.IsOwnedByLocalPlayer())
|
||||
return false;
|
||||
|
||||
return profile.Character.MatchesIgnoringOwnership(actorIdentifier);
|
||||
return profile.Characters.Any(x => x.MatchesIgnoringOwnership(actorIdentifier));
|
||||
}
|
||||
|
||||
if (_templateEditorManager.IsEditorActive && _templateEditorManager.EditorProfile.Enabled && IsProfileAppliesToCurrentActor(_templateEditorManager.EditorProfile))
|
||||
@@ -585,7 +633,7 @@ public partial class ProfileManager : IDisposable
|
||||
if (!Profiles.Remove(profile))
|
||||
return;
|
||||
|
||||
_logger.Debug($"ProfileManager.OnArmatureChange: Removed unused temporary profile for {profile.Character.Incognito(null)}");
|
||||
_logger.Debug($"ProfileManager.OnArmatureChange: Removed unused temporary profile for {profile.Characters[0].Incognito(null)}");
|
||||
|
||||
_event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, profile, null);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class TemplateEditorManager : IDisposable
|
||||
/// <summary>
|
||||
/// Name of the preview character for the editor
|
||||
/// </summary>
|
||||
public ActorIdentifier Character => EditorProfile.Character;
|
||||
public ActorIdentifier Character => EditorProfile.Characters[0];
|
||||
|
||||
/// <summary>
|
||||
/// Checks if preview character exists at the time of call
|
||||
@@ -107,8 +107,9 @@ public class TemplateEditorManager : IDisposable
|
||||
Enabled = false,
|
||||
Name = "Template editor profile",
|
||||
ProfileType = ProfileType.Editor,
|
||||
Character = configuration.EditorConfiguration.PreviewCharacter
|
||||
};
|
||||
|
||||
EditorProfile.Characters.Add(configuration.EditorConfiguration.PreviewCharacter);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -194,9 +195,10 @@ public class TemplateEditorManager : IDisposable
|
||||
|
||||
private bool ChangeEditorCharacterInternal(ActorIdentifier character)
|
||||
{
|
||||
_logger.Debug($"Changing character name for editor profile from {EditorProfile.Character.Incognito(null)} to {character.Incognito(null)}");
|
||||
_logger.Debug($"Changing character name for editor profile from {EditorProfile.Characters.FirstOrDefault().Incognito(null)} to {character.Incognito(null)}");
|
||||
|
||||
EditorProfile.Character = character;
|
||||
EditorProfile.Characters.Clear();
|
||||
EditorProfile.Characters.Add(character);
|
||||
|
||||
_configuration.EditorConfiguration.PreviewCharacter = character;
|
||||
_configuration.Save();
|
||||
|
||||
@@ -134,12 +134,11 @@ public class StateMonitoringTab
|
||||
private void DrawSingleProfile(string prefix, Profile profile)
|
||||
{
|
||||
string name = profile.Name;
|
||||
string characterName = profile.Character.Type == Penumbra.GameData.Enums.IdentifierType.Owned ?
|
||||
profile.Character.ToNameWithoutOwnerName() : profile.Character.ToString();
|
||||
string characterName = string.Join(',', profile.Characters.Select(x => x.ToNameWithoutOwnerName().Incognify()));
|
||||
|
||||
#if INCOGNIFY_STRINGS
|
||||
name = name.Incognify();
|
||||
characterName = characterName.Incognify();
|
||||
//characterName = characterName.Incognify();
|
||||
#endif
|
||||
|
||||
var show = ImGui.CollapsingHeader($"[{(profile.Enabled ? "E" : "D")}] {name} on {characterName} [{profile.ProfileType}] [{profile.UniqueId}]###{prefix}-profile-{profile.UniqueId}");
|
||||
|
||||
@@ -17,6 +17,7 @@ using CustomizePlus.Profiles.Data;
|
||||
using CustomizePlus.Game.Services;
|
||||
using CustomizePlus.Profiles.Events;
|
||||
using CustomizePlus.GameData.Extensions;
|
||||
using System.Linq;
|
||||
|
||||
namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Profiles;
|
||||
|
||||
@@ -133,7 +134,8 @@ public class ProfileFileSystemSelector : FileSystemSelector<Profile, ProfileStat
|
||||
case ProfileChanged.Type.Deleted:
|
||||
case ProfileChanged.Type.Renamed:
|
||||
case ProfileChanged.Type.Toggled:
|
||||
case ProfileChanged.Type.ChangedCharacter:
|
||||
case ProfileChanged.Type.AddedCharacter:
|
||||
case ProfileChanged.Type.RemovedCharacter:
|
||||
case ProfileChanged.Type.ReloadedAll:
|
||||
SetFilterDirty();
|
||||
break;
|
||||
@@ -239,10 +241,13 @@ public class ProfileFileSystemSelector : FileSystemSelector<Profile, ProfileStat
|
||||
return false;
|
||||
}
|
||||
|
||||
var identifier = _gameObjectService.GetCurrentPlayerActorIdentifier();
|
||||
if (leaf.Value.Enabled)
|
||||
state.Color = leaf.Value.Character.MatchesIgnoringOwnership(_gameObjectService.GetCurrentPlayerActorIdentifier()) ? ColorId.LocalCharacterEnabledProfile : ColorId.EnabledProfile;
|
||||
state.Color = leaf.Value.Characters.Any(x => x.MatchesIgnoringOwnership(identifier)) ? ColorId.LocalCharacterEnabledProfile : ColorId.EnabledProfile;
|
||||
else
|
||||
state.Color = leaf.Value.Character.MatchesIgnoringOwnership(_gameObjectService.GetCurrentPlayerActorIdentifier()) ? ColorId.LocalCharacterDisabledProfile : ColorId.DisabledProfile;
|
||||
state.Color = leaf.Value.Characters.Any(x => x.MatchesIgnoringOwnership(identifier)) ? ColorId.LocalCharacterDisabledProfile : ColorId.DisabledProfile;
|
||||
|
||||
//todo: missing actor color
|
||||
|
||||
return ApplyStringFilters(leaf, leaf.Value);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using Penumbra.GameData.Actors;
|
||||
using Penumbra.String;
|
||||
using static FFXIVClientStructs.FFXIV.Client.LayoutEngine.ILayoutInstance;
|
||||
using CustomizePlus.GameData.Extensions;
|
||||
using CustomizePlus.Core.Extensions;
|
||||
|
||||
namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Profiles;
|
||||
|
||||
@@ -151,6 +152,13 @@ public class ProfilePanel
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
var isShouldDraw = ImGui.CollapsingHeader("Character settings");
|
||||
|
||||
if (isShouldDraw)
|
||||
DrawCharacterArea();
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
DrawTemplateArea();
|
||||
}
|
||||
}
|
||||
@@ -204,25 +212,21 @@ public class ProfilePanel
|
||||
}
|
||||
else
|
||||
ImGui.TextUnformatted(_selector.Selected!.Incognito);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TableNextRow();
|
||||
|
||||
ImGuiUtil.DrawFrameColumn("Character");
|
||||
ImGui.TableNextColumn();
|
||||
width = new Vector2(ImGui.GetContentRegionAvail().X - ImGui.CalcTextSize("Limit to my creatures").X - 68, 0);
|
||||
private void DrawCharacterArea()
|
||||
{
|
||||
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)))
|
||||
{
|
||||
var width = new Vector2(ImGui.GetContentRegionAvail().X - ImGui.CalcTextSize("Limit to my creatures").X - 68, 0);
|
||||
|
||||
ImGui.SetNextItemWidth(width.X);
|
||||
|
||||
if (!_selector.IncognitoMode)
|
||||
bool appliesToMultiple = _manager.DefaultProfile == _selector.Selected || _manager.DefaultLocalPlayerProfile == _selector.Selected;
|
||||
using (ImRaii.Disabled(appliesToMultiple))
|
||||
{
|
||||
bool showMultipleMessage = false;
|
||||
if (_manager.DefaultProfile != _selector.Selected && _manager.DefaultLocalPlayerProfile != _selector.Selected)
|
||||
{
|
||||
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.Separator();
|
||||
|
||||
_actorAssignmentUi.DrawWorldCombo(width.X / 2);
|
||||
ImGui.SameLine();
|
||||
_actorAssignmentUi.DrawPlayerInput(width.X / 2);
|
||||
@@ -230,21 +234,21 @@ public class ProfilePanel
|
||||
var buttonWidth = new Vector2(165 * ImGuiHelpers.GlobalScale - ImGui.GetStyle().ItemSpacing.X / 2, 0);
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply to player character", buttonWidth, string.Empty, !_actorAssignmentUi.CanSetPlayer))
|
||||
_manager.ChangeCharacter(_selector.Selected!, _actorAssignmentUi.PlayerIdentifier);
|
||||
_manager.AddCharacter(_selector.Selected!, _actorAssignmentUi.PlayerIdentifier);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply to retainer", buttonWidth, string.Empty, !_actorAssignmentUi.CanSetRetainer))
|
||||
_manager.ChangeCharacter(_selector.Selected!, _actorAssignmentUi.RetainerIdentifier);
|
||||
_manager.AddCharacter(_selector.Selected!, _actorAssignmentUi.RetainerIdentifier);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply to mannequin", buttonWidth, string.Empty, !_actorAssignmentUi.CanSetMannequin))
|
||||
_manager.ChangeCharacter(_selector.Selected!, _actorAssignmentUi.MannequinIdentifier);
|
||||
_manager.AddCharacter(_selector.Selected!, _actorAssignmentUi.MannequinIdentifier);
|
||||
|
||||
var currentPlayer = _actorManager.GetCurrentPlayer();
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply to current character", buttonWidth, string.Empty, !currentPlayer.IsValid))
|
||||
_manager.ChangeCharacter(_selector.Selected!, currentPlayer);
|
||||
_manager.AddCharacter(_selector.Selected!, currentPlayer);
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
@@ -253,14 +257,8 @@ public class ProfilePanel
|
||||
_actorAssignmentUi.DrawNpcInput(width.X / 2);
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply to selected NPC", buttonWidth, string.Empty, !_actorAssignmentUi.CanSetNpc))
|
||||
_manager.ChangeCharacter(_selector.Selected!, _actorAssignmentUi.NpcIdentifier);
|
||||
|
||||
_manager.AddCharacter(_selector.Selected!, _actorAssignmentUi.NpcIdentifier);
|
||||
}
|
||||
else
|
||||
ImGui.TextUnformatted("Applies to multiple targets");
|
||||
}
|
||||
else
|
||||
ImGui.TextUnformatted("Incognito active");
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
@@ -299,13 +297,64 @@ public class ProfilePanel
|
||||
ImGui.PopStyleColor();
|
||||
ImGuiUtil.HoverTooltip("Can only be changed when both currently selected and profile where this checkbox is checked are disabled.");
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
using var dis = ImRaii.Disabled(appliesToMultiple);
|
||||
using var table = ImRaii.Table("CharacterTable", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY, new Vector2(ImGui.GetContentRegionAvail().X, 150));
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
ImGui.TableSetupColumn("##charaDel", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight());
|
||||
ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthFixed, 320 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
if(appliesToMultiple)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted("Applies to multiple targets");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//warn: .ToList() might be performance critical at some point
|
||||
//the copying via ToList is done because manipulations with .Templates list result in "Collection was modified" exception here
|
||||
var charas = _selector.Selected!.Characters.WithIndex().ToList();
|
||||
|
||||
if (charas.Count == 0)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted("No characters are associated with this profile");
|
||||
}
|
||||
|
||||
foreach (var (character, idx) in charas)
|
||||
{
|
||||
using var id = ImRaii.PushId(idx);
|
||||
ImGui.TableNextColumn();
|
||||
var keyValid = _configuration.UISettings.DeleteTemplateModifier.IsActive();
|
||||
var tt = keyValid
|
||||
? "Remove this character from the profile."
|
||||
: $"Remove this character from the profile.\nHold {_configuration.UISettings.DeleteTemplateModifier} to remove.";
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), new Vector2(ImGui.GetFrameHeight()), tt, !keyValid, true))
|
||||
_endAction = () => _manager.DeleteCharacter(_selector.Selected!, character);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted(!_selector.IncognitoMode ? $"{character.ToNameWithoutOwnerName()}{character.TypeToString()}" : "Incognito");
|
||||
}
|
||||
}
|
||||
|
||||
_endAction?.Invoke();
|
||||
_endAction = null;
|
||||
}
|
||||
|
||||
private void DrawTemplateArea()
|
||||
{
|
||||
using var table = ImRaii.Table("SetTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY);
|
||||
using var table = ImRaii.Table("TemplateTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user