Added ability to apply profile to several characters

This commit is contained in:
RisaDev
2024-10-19 02:55:38 +03:00
parent 3ff5806322
commit 7085cf616c
14 changed files with 324 additions and 164 deletions

View File

@@ -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>

View File

@@ -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();
}

View File

@@ -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>()
};

View File

@@ -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;
}

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -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

View File

@@ -15,7 +15,9 @@ public sealed class ProfileChanged() : EventWrapper<ProfileChanged.Type, Profile
Deleted,
Renamed,
Toggled,
ChangedCharacter,
AddedCharacter,
RemovedCharacter,
//ChangedCharacter,
AddedTemplate,
RemovedTemplate,
MovedTemplate,

View File

@@ -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");

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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}");

View File

@@ -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);
}

View File

@@ -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;