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,13 +457,16 @@ public unsafe sealed class ArmatureManager : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
if (!profile.Character.IsValid)
|
||||
return;
|
||||
|
||||
foreach (var armature in GetArmaturesForCharacter(profile.Character))
|
||||
foreach(var character in profile.Characters)
|
||||
{
|
||||
armature.IsPendingProfileRebind = true;
|
||||
_logger.Debug($"ArmatureManager.OnProfileChange profile {profile} toggled, planning rebind for armature {armature}");
|
||||
if (!character.IsValid)
|
||||
continue;
|
||||
|
||||
foreach (var armature in GetArmaturesForCharacter(character))
|
||||
{
|
||||
armature.IsPendingProfileRebind = true;
|
||||
_logger.Debug($"ArmatureManager.OnProfileChange profile {profile} toggled, planning rebind for armature {armature}");
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -470,9 +474,6 @@ public unsafe sealed class ArmatureManager : IDisposable
|
||||
|
||||
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,21 +490,28 @@ 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;
|
||||
|
||||
if (armature.Profile == profile)
|
||||
return;
|
||||
var armature = Armatures[character];
|
||||
|
||||
armature.UpdateLastSeen();
|
||||
if (armature.Profile == profile)
|
||||
return;
|
||||
|
||||
armature.IsPendingProfileRebind = true;
|
||||
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,11 +254,34 @@ 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)
|
||||
{
|
||||
_logger.Debug($"\t-> {otherProfile} disabled");
|
||||
SetEnabled(otherProfile, false);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,108 +212,149 @@ 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);
|
||||
|
||||
ImGui.SetNextItemWidth(width.X);
|
||||
|
||||
if (!_selector.IncognitoMode)
|
||||
{
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply to retainer", buttonWidth, string.Empty, !_actorAssignmentUi.CanSetRetainer))
|
||||
_manager.ChangeCharacter(_selector.Selected!, _actorAssignmentUi.RetainerIdentifier);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply to mannequin", buttonWidth, string.Empty, !_actorAssignmentUi.CanSetMannequin))
|
||||
_manager.ChangeCharacter(_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);
|
||||
|
||||
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))
|
||||
_manager.ChangeCharacter(_selector.Selected!, _actorAssignmentUi.NpcIdentifier);
|
||||
|
||||
}
|
||||
else
|
||||
ImGui.TextUnformatted("Applies to multiple targets");
|
||||
}
|
||||
else
|
||||
ImGui.TextUnformatted("Incognito active");
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
var isDefaultLP = _manager.DefaultLocalPlayerProfile == _selector.Selected;
|
||||
var isDefaultLPOrCurrentProfilesEnabled = (_manager.DefaultLocalPlayerProfile?.Enabled ?? false) || (_selector.Selected?.Enabled ?? false);
|
||||
using (ImRaii.Disabled(isDefaultLPOrCurrentProfilesEnabled))
|
||||
{
|
||||
if (ImGui.Checkbox("##DefaultLocalPlayerProfile", ref isDefaultLP))
|
||||
_manager.SetDefaultLocalPlayerProfile(isDefaultLP ? _selector.Selected! : null);
|
||||
ImGuiUtil.LabeledHelpMarker("Apply to any character you are logged in with",
|
||||
"Whether the templates in this profile should be applied to any character you are currently logged in with.\r\nTakes priority over the next option for said character.\r\nThis setting cannot be applied to multiple profiles.");
|
||||
}
|
||||
if (isDefaultLPOrCurrentProfilesEnabled)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, Constants.Colors.Warning);
|
||||
ImGuiUtil.PrintIcon(FontAwesomeIcon.ExclamationTriangle);
|
||||
ImGui.PopStyleColor();
|
||||
ImGuiUtil.HoverTooltip("Can only be changed when both currently selected and profile where this checkbox is checked are disabled.");
|
||||
}
|
||||
|
||||
var isDefault = _manager.DefaultProfile == _selector.Selected;
|
||||
var isDefaultOrCurrentProfilesEnabled = (_manager.DefaultProfile?.Enabled ?? false) || (_selector.Selected?.Enabled ?? false);
|
||||
using (ImRaii.Disabled(isDefaultOrCurrentProfilesEnabled))
|
||||
{
|
||||
if (ImGui.Checkbox("##DefaultProfile", ref isDefault))
|
||||
_manager.SetDefaultProfile(isDefault ? _selector.Selected! : null);
|
||||
ImGuiUtil.LabeledHelpMarker("Apply to all players and retainers",
|
||||
"Whether the templates in this profile are applied to all players and retainers without a specific profile.\r\nThis setting cannot be applied to multiple profiles.");
|
||||
}
|
||||
if (isDefaultOrCurrentProfilesEnabled)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, Constants.Colors.Warning);
|
||||
ImGuiUtil.PrintIcon(FontAwesomeIcon.ExclamationTriangle);
|
||||
ImGui.PopStyleColor();
|
||||
ImGuiUtil.HoverTooltip("Can only be changed when both currently selected and profile where this checkbox is checked are disabled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
bool appliesToMultiple = _manager.DefaultProfile == _selector.Selected || _manager.DefaultLocalPlayerProfile == _selector.Selected;
|
||||
using (ImRaii.Disabled(appliesToMultiple))
|
||||
{
|
||||
_actorAssignmentUi.DrawWorldCombo(width.X / 2);
|
||||
ImGui.SameLine();
|
||||
_actorAssignmentUi.DrawPlayerInput(width.X / 2);
|
||||
|
||||
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.AddCharacter(_selector.Selected!, _actorAssignmentUi.PlayerIdentifier);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply to retainer", buttonWidth, string.Empty, !_actorAssignmentUi.CanSetRetainer))
|
||||
_manager.AddCharacter(_selector.Selected!, _actorAssignmentUi.RetainerIdentifier);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply to mannequin", buttonWidth, string.Empty, !_actorAssignmentUi.CanSetMannequin))
|
||||
_manager.AddCharacter(_selector.Selected!, _actorAssignmentUi.MannequinIdentifier);
|
||||
|
||||
var currentPlayer = _actorManager.GetCurrentPlayer();
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply to current character", buttonWidth, string.Empty, !currentPlayer.IsValid))
|
||||
_manager.AddCharacter(_selector.Selected!, 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))
|
||||
_manager.AddCharacter(_selector.Selected!, _actorAssignmentUi.NpcIdentifier);
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
var isDefaultLP = _manager.DefaultLocalPlayerProfile == _selector.Selected;
|
||||
var isDefaultLPOrCurrentProfilesEnabled = (_manager.DefaultLocalPlayerProfile?.Enabled ?? false) || (_selector.Selected?.Enabled ?? false);
|
||||
using (ImRaii.Disabled(isDefaultLPOrCurrentProfilesEnabled))
|
||||
{
|
||||
if (ImGui.Checkbox("##DefaultLocalPlayerProfile", ref isDefaultLP))
|
||||
_manager.SetDefaultLocalPlayerProfile(isDefaultLP ? _selector.Selected! : null);
|
||||
ImGuiUtil.LabeledHelpMarker("Apply to any character you are logged in with",
|
||||
"Whether the templates in this profile should be applied to any character you are currently logged in with.\r\nTakes priority over the next option for said character.\r\nThis setting cannot be applied to multiple profiles.");
|
||||
}
|
||||
if (isDefaultLPOrCurrentProfilesEnabled)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, Constants.Colors.Warning);
|
||||
ImGuiUtil.PrintIcon(FontAwesomeIcon.ExclamationTriangle);
|
||||
ImGui.PopStyleColor();
|
||||
ImGuiUtil.HoverTooltip("Can only be changed when both currently selected and profile where this checkbox is checked are disabled.");
|
||||
}
|
||||
|
||||
var isDefault = _manager.DefaultProfile == _selector.Selected;
|
||||
var isDefaultOrCurrentProfilesEnabled = (_manager.DefaultProfile?.Enabled ?? false) || (_selector.Selected?.Enabled ?? false);
|
||||
using (ImRaii.Disabled(isDefaultOrCurrentProfilesEnabled))
|
||||
{
|
||||
if (ImGui.Checkbox("##DefaultProfile", ref isDefault))
|
||||
_manager.SetDefaultProfile(isDefault ? _selector.Selected! : null);
|
||||
ImGuiUtil.LabeledHelpMarker("Apply to all players and retainers",
|
||||
"Whether the templates in this profile are applied to all players and retainers without a specific profile.\r\nThis setting cannot be applied to multiple profiles.");
|
||||
}
|
||||
if (isDefaultOrCurrentProfilesEnabled)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, Constants.Colors.Warning);
|
||||
ImGuiUtil.PrintIcon(FontAwesomeIcon.ExclamationTriangle);
|
||||
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