From 2d40fff844638b762371205e6046adeef6e4d6e8 Mon Sep 17 00:00:00 2001 From: RisaDev <151885272+RisaDev@users.noreply.github.com> Date: Mon, 7 Oct 2024 01:11:20 +0300 Subject: [PATCH] More work on profile character assignment rewrite. Added ability to apply profile to any currently logged in character Functional UI for player character, retainers and mannequins Almost completely switched to using ActorIdentifier instead of character name Migration code for ActorIdentifier instead of character names IPC is not functional for now (see todos) --- CustomizePlus/Api/CustomizePlusIpc.Profile.cs | 4 +- CustomizePlus/Api/Data/IPCCharacterProfile.cs | 5 +- .../Armatures/Services/ArmatureManager.cs | 31 +++- CustomizePlus/Core/ServiceManagerBuilder.cs | 3 +- .../Game/Services/GameObjectService.cs | 5 + CustomizePlus/Profiles/Data/Profile.cs | 17 +- .../Profiles/Events/ProfileChanged.cs | 3 +- .../Profiles/ProfileManager.ProfileLoading.cs | 38 ++-- CustomizePlus/Profiles/ProfileManager.cs | 85 ++++++--- .../UI/Windows/Controls/ActorAssignmentUi.cs | 172 ++++++++++-------- .../Profiles/ProfileFileSystemSelector.cs | 6 +- .../MainWindow/Tabs/Profiles/ProfilePanel.cs | 88 +++++++-- 12 files changed, 292 insertions(+), 165 deletions(-) diff --git a/CustomizePlus/Api/CustomizePlusIpc.Profile.cs b/CustomizePlus/Api/CustomizePlusIpc.Profile.cs index 952927f..221bbf6 100644 --- a/CustomizePlus/Api/CustomizePlusIpc.Profile.cs +++ b/CustomizePlus/Api/CustomizePlusIpc.Profile.cs @@ -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.CharacterName.Text, x.Enabled); + return (x.UniqueId, x.Name.Text, path, x.Character.ToNameWithoutOwnerName(), x.Enabled); //todo: proper update to v5 }) .ToList(); } @@ -134,7 +134,7 @@ public partial class CustomizePlusIpc if (actor == null || !actor.Value.Valid || !actor.Value.IsCharacter) return ((int)ErrorCode.InvalidCharacter, null); - var profile = _profileManager.GetProfileByCharacterName(actor.Value.Utf8Name.ToString(), true); + var profile = _profileManager.GetProfileByActor(actor.Value, true); if (profile == null) return ((int)ErrorCode.ProfileNotFound, null); diff --git a/CustomizePlus/Api/Data/IPCCharacterProfile.cs b/CustomizePlus/Api/Data/IPCCharacterProfile.cs index 727e73e..c0f75c2 100644 --- a/CustomizePlus/Api/Data/IPCCharacterProfile.cs +++ b/CustomizePlus/Api/Data/IPCCharacterProfile.cs @@ -1,5 +1,6 @@ using CustomizePlus.Configuration.Data.Version3; using CustomizePlus.Core.Data; +using CustomizePlus.GameData.Extensions; using CustomizePlus.Profiles.Data; using CustomizePlus.Templates.Data; using System; @@ -23,7 +24,7 @@ public class IPCCharacterProfile { var ipcProfile = new IPCCharacterProfile { - CharacterName = profile.CharacterName, + CharacterName = profile.Character.ToNameWithoutOwnerName(), //todo: proper update to v5 Bones = new Dictionary() }; @@ -48,7 +49,7 @@ public class IPCCharacterProfile var fullProfile = new Profile { Name = $"{profile.CharacterName}'s IPC profile", - CharacterName = profile.CharacterName, + // CharacterName = profile.CharacterName, //todo: proper update to v5 CreationDate = DateTimeOffset.UtcNow, ModifiedDate = DateTimeOffset.UtcNow, Enabled = true, diff --git a/CustomizePlus/Armatures/Services/ArmatureManager.cs b/CustomizePlus/Armatures/Services/ArmatureManager.cs index 348e4e2..ece42c0 100644 --- a/CustomizePlus/Armatures/Services/ArmatureManager.cs +++ b/CustomizePlus/Armatures/Services/ArmatureManager.cs @@ -443,9 +443,10 @@ 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.ChangedCharacterName && + type is not ProfileChanged.Type.ChangedCharacter && type is not ProfileChanged.Type.ChangedDefaultProfile && - type is not ProfileChanged.Type.LimitLookupToOwnedChanged) + type is not ProfileChanged.Type.LimitLookupToOwnedChanged && + type is not ProfileChanged.Type.ApplyToCurrentlyActiveCharacterChanged) return; if (type == ProfileChanged.Type.ChangedDefaultProfile) @@ -488,10 +489,10 @@ public unsafe sealed class ArmatureManager : IDisposable return; } - if (string.IsNullOrWhiteSpace(profile.CharacterName)) + if (!profile.Character.IsValid) return; - foreach (var armature in GetArmaturesForCharacterName(profile.CharacterName)) + foreach (var armature in GetArmaturesForCharacter(profile.Character)) { armature.IsPendingProfileRebind = true; _logger.Debug($"ArmatureManager.OnProfileChange profile {profile} toggled, planning rebind for armature {armature}"); @@ -502,10 +503,10 @@ public unsafe sealed class ArmatureManager : IDisposable if (type == ProfileChanged.Type.TemporaryProfileAdded) { - if (!profile.TemporaryActor.IsValid || !Armatures.ContainsKey(profile.TemporaryActor)) + if (!profile.Character.IsValid || !Armatures.ContainsKey(profile.Character)) //todo: any world support return; - var armature = Armatures[profile.TemporaryActor]; + var armature = Armatures[profile.Character]; if (armature.Profile == profile) return; @@ -518,10 +519,11 @@ public unsafe sealed class ArmatureManager : IDisposable return; } - if (type == ProfileChanged.Type.ChangedCharacterName || + if (type == ProfileChanged.Type.ChangedCharacter || type == ProfileChanged.Type.Deleted || type == ProfileChanged.Type.TemporaryProfileDeleted || - type == ProfileChanged.Type.LimitLookupToOwnedChanged) + type == ProfileChanged.Type.LimitLookupToOwnedChanged || + type == ProfileChanged.Type.ApplyToCurrentlyActiveCharacterChanged) { if (profile.Armatures.Count == 0) return; @@ -534,7 +536,7 @@ public unsafe sealed class ArmatureManager : IDisposable armature.IsPendingProfileRebind = true; } - _logger.Debug($"ArmatureManager.OnProfileChange CCN/DEL/TPD/LLTOC, armature rebind scheduled: {type}, data payload: {arg3?.ToString()?.Incognify()}, profile: {profile.Name.Text.Incognify()}->{profile.Enabled}"); + _logger.Debug($"ArmatureManager.OnProfileChange CC/DEL/TPD/LLTOC/ATCACC, armature rebind scheduled: {type}, data payload: {arg3?.ToString()?.Incognify()}, profile: {profile.Name.Text.Incognify()}->{profile.Enabled}"); return; } @@ -558,4 +560,15 @@ public unsafe sealed class ArmatureManager : IDisposable yield return kvPair.Value; } } + + private IEnumerable GetArmaturesForCharacter(ActorIdentifier actorIdentifier) + { + foreach (var kvPair in Armatures) + { + (var armatureActorIdentifier, _) = _gameObjectService.GetTrueActorForSpecialTypeActor(kvPair.Key); + + if (armatureActorIdentifier.IsValid && armatureActorIdentifier.Matches(armatureActorIdentifier)) + yield return kvPair.Value; + } + } } \ No newline at end of file diff --git a/CustomizePlus/Core/ServiceManagerBuilder.cs b/CustomizePlus/Core/ServiceManagerBuilder.cs index cb45dd4..90440d5 100644 --- a/CustomizePlus/Core/ServiceManagerBuilder.cs +++ b/CustomizePlus/Core/ServiceManagerBuilder.cs @@ -206,7 +206,8 @@ public static class ServiceManagerBuilder .AddSingleton() .AddSingleton() .AddSingleton(p => new CutsceneResolver(idx => (short)p.GetRequiredService().GetParentIndex(idx))) - .AddSingleton(); + .AddSingleton() + .AddSingleton< DictBNpcENpc>(); return services; } diff --git a/CustomizePlus/Game/Services/GameObjectService.cs b/CustomizePlus/Game/Services/GameObjectService.cs index 6fac33c..46fdd22 100644 --- a/CustomizePlus/Game/Services/GameObjectService.cs +++ b/CustomizePlus/Game/Services/GameObjectService.cs @@ -31,6 +31,11 @@ public class GameObjectService _configuration = configuration; } + public ActorIdentifier GetCurrentPlayerActorIdentifier() + { + return _objectManager.PlayerData.Identifier; + } + public string GetCurrentPlayerName() { return _objectManager.PlayerData.Identifier.ToName(); diff --git a/CustomizePlus/Profiles/Data/Profile.cs b/CustomizePlus/Profiles/Data/Profile.cs index bb97d2c..faf755d 100644 --- a/CustomizePlus/Profiles/Data/Profile.cs +++ b/CustomizePlus/Profiles/Data/Profile.cs @@ -21,7 +21,7 @@ namespace CustomizePlus.Profiles.Data; /// public sealed class Profile : ISavable { - public const int Version = 4; + public const int Version = 5; private static int _nextGlobalId; @@ -29,10 +29,11 @@ public sealed class Profile : ISavable public List Armatures = new(); - [Obsolete("To be removed")] + [Obsolete("To be removed in the future versions")] public LowerString CharacterName { get; set; } = LowerString.Empty; public ActorIdentifier Character { get; set; } = ActorIdentifier.Invalid; + public bool ApplyToCurrentlyActiveCharacter { get; set; } public LowerString Name { get; set; } = LowerString.Empty; @@ -59,10 +60,10 @@ public sealed class Profile : ISavable /// public bool IsTemporary => ProfileType == ProfileType.Temporary; - /// + /* /// /// Identificator specifying specific actor this profile applies to, only works for temporary profiles /// - public ActorIdentifier TemporaryActor { get; set; } = ActorIdentifier.Invalid; + public ActorIdentifier TemporaryActor { get; set; } = ActorIdentifier.Invalid;*/ public string Incognito => UniqueId.ToString()[..8]; @@ -78,8 +79,9 @@ public sealed class Profile : ISavable /// public Profile(Profile original) : this() { - CharacterName = original.CharacterName; + Character = original.Character; LimitLookupToOwnedObjects = original.LimitLookupToOwnedObjects; + ApplyToCurrentlyActiveCharacter = original.ApplyToCurrentlyActiveCharacter; foreach (var template in original.Templates) { @@ -89,7 +91,7 @@ public sealed class Profile : ISavable public override string ToString() { - return $"Profile '{Name.Text.Incognify()}' on {CharacterName.Text.Incognify()} [{UniqueId}]"; + return $"Profile '{Name.Text.Incognify()}' on {Character.Incognito(null)} [{UniqueId}]"; } #region Serialization @@ -103,7 +105,8 @@ public sealed class Profile : ISavable ["CreationDate"] = CreationDate, ["ModifiedDate"] = ModifiedDate, ["CharacterName"] = CharacterName.Text, - //["Character"] = Character.ToJson(), + ["Character"] = Character.ToJson(), + ["ApplyToCurrentlyActiveCharacter"] = ApplyToCurrentlyActiveCharacter, ["Name"] = Name.Text, ["LimitLookupToOwnedObjects"] = LimitLookupToOwnedObjects, ["Enabled"] = Enabled, diff --git a/CustomizePlus/Profiles/Events/ProfileChanged.cs b/CustomizePlus/Profiles/Events/ProfileChanged.cs index f8c1b81..1869311 100644 --- a/CustomizePlus/Profiles/Events/ProfileChanged.cs +++ b/CustomizePlus/Profiles/Events/ProfileChanged.cs @@ -15,7 +15,7 @@ public sealed class ProfileChanged() : EventWrapper LoadV4(obj), - // 5 => LoadV5(obj), - 4 => LoadV5(obj), + 4 => LoadV4(obj), + 5 => LoadV5(obj), _ => throw new Exception("The profile to be loaded has no valid Version."), }; } private Profile LoadV4(JObject obj) { - var characterName = new LowerString(obj["CharacterName"]?.ToObject()?.Trim() ?? throw new ArgumentNullException("CharacterName")); + var profile = LoadProfileV4V5(obj); - ByteString.FromString(characterName, out var nameByteString); - var character = _actorManager.CreatePlayer(nameByteString, WorldId.AnyWorld); //todo: detect type - - obj["Character"] = character.ToJson(); - - var profile = LoadV5(obj); - - profile.ModifiedDate = DateTimeOffset.UtcNow; - _saveService.ImmediateSave(profile); + profile.CharacterName = new LowerString(obj["CharacterName"]?.ToObject()?.Trim() ?? throw new ArgumentNullException("CharacterName")); return profile; } private Profile LoadV5(JObject obj) + { + var profile = LoadProfileV4V5(obj); + + var character = _actorManager.FromJson(obj["Character"] as JObject); + + profile.Character = character; + profile.ApplyToCurrentlyActiveCharacter = obj["ApplyToCurrentlyActiveCharacter"]?.ToObject() ?? false; + profile.CharacterName = new LowerString(obj["CharacterName"]?.ToObject()?.Trim() ?? throw new ArgumentNullException("CharacterName")); //temp + + return profile; + } + + //V4 and V5 are mostly not different, so common loading logic is here + private Profile LoadProfileV4V5(JObject obj) { var creationDate = obj["CreationDate"]?.ToObject() ?? throw new ArgumentNullException("CreationDate"); - /*var character = _actorManager.FromJson(obj["Character"] as JObject); - - if (!character.IsValid) - throw new ArgumentException("Character");*/ - var profile = new Profile() { CreationDate = creationDate, UniqueId = obj["UniqueId"]?.ToObject() ?? throw new ArgumentNullException("UniqueId"), Name = new LowerString(obj["Name"]?.ToObject()?.Trim() ?? throw new ArgumentNullException("Name")), - //Character = character, - CharacterName = new LowerString(obj["CharacterName"]?.ToObject()?.Trim() ?? throw new ArgumentNullException("CharacterName")), LimitLookupToOwnedObjects = obj["LimitLookupToOwnedObjects"]?.ToObject() ?? throw new ArgumentNullException("LimitLookupToOwnedObjects"), Enabled = obj["Enabled"]?.ToObject() ?? throw new ArgumentNullException("Enabled"), ModifiedDate = obj["ModifiedDate"]?.ToObject() ?? creationDate, diff --git a/CustomizePlus/Profiles/ProfileManager.cs b/CustomizePlus/Profiles/ProfileManager.cs index 2413293..19c41df 100644 --- a/CustomizePlus/Profiles/ProfileManager.cs +++ b/CustomizePlus/Profiles/ProfileManager.cs @@ -168,26 +168,24 @@ public partial class ProfileManager : IDisposable } /// - /// Change character name for profile + /// Change character associated with profile /// - public void ChangeCharacterName(Profile profile, string newName) + public void ChangeCharacter(Profile profile, ActorIdentifier actorIdentifier) { - newName = newName.Trim(); - - var oldName = profile.CharacterName.Text; - if (oldName == newName) + if (!actorIdentifier.IsValid || actorIdentifier.Matches(profile.Character)) return; - profile.CharacterName = newName; + var oldCharacter = profile.Character; + profile.Character = actorIdentifier; //Called so all other active profiles for new character name get disabled //saving is performed there - SetEnabled(profile, profile.Enabled, true); + //SetEnabled(profile, profile.Enabled, true); //todo SaveProfile(profile); - _logger.Debug($"Changed character name for profile {profile.UniqueId}."); - _event.Invoke(ProfileChanged.Type.ChangedCharacterName, profile, oldName); + _logger.Debug($"Changed character for profile {profile.UniqueId}."); + _event.Invoke(ProfileChanged.Type.ChangedCharacter, profile, oldCharacter); } /// @@ -229,7 +227,7 @@ public partial class ProfileManager : IDisposable _logger.Debug($"Setting {profile} as enabled..."); foreach (var otherProfile in Profiles - .Where(x => x.CharacterName == profile.CharacterName && x != profile && x.Enabled && !x.IsTemporary)) + .Where(x => x.Character.Matches(profile.Character) && x != profile && x.Enabled && !x.IsTemporary)) { _logger.Debug($"\t-> {otherProfile} disabled"); SetEnabled(otherProfile, false); @@ -268,6 +266,18 @@ public partial class ProfileManager : IDisposable } } + public void SetApplyToCurrentlyActiveCharacter(Profile profile, bool value) + { + if (profile.ApplyToCurrentlyActiveCharacter != value) + { + profile.ApplyToCurrentlyActiveCharacter = value; + + SaveProfile(profile); + + _event.Invoke(ProfileChanged.Type.ApplyToCurrentlyActiveCharacterChanged, profile, value); + } + } + public void DeleteTemplate(Profile profile, int templateIndex) { _logger.Debug($"Deleting template #{templateIndex} from {profile}..."); @@ -342,6 +352,7 @@ public partial class ProfileManager : IDisposable _event.Invoke(ProfileChanged.Type.ChangedDefaultProfile, profile, previousProfile); } + //warn: temporary profile system does not support any world identifiers public void AddTemporaryProfile(Profile profile, Actor actor/*, Template template*/) { if (!actor.Identifier(_actorManager, out var identifier)) @@ -349,14 +360,13 @@ public partial class ProfileManager : IDisposable profile.Enabled = true; profile.ProfileType = ProfileType.Temporary; - profile.TemporaryActor = identifier; - profile.CharacterName = identifier.ToNameWithoutOwnerName(); + profile.Character = identifier; profile.LimitLookupToOwnedObjects = false; - var existingProfile = Profiles.FirstOrDefault(x => x.CharacterName.Lower == profile.CharacterName.Lower && x.IsTemporary); + var existingProfile = Profiles.FirstOrDefault(x => x.Character.Matches(profile.Character) && x.IsTemporary); if (existingProfile != null) { - _logger.Debug($"Temporary profile for {existingProfile.CharacterName} already exists, removing..."); + _logger.Debug($"Temporary profile for {existingProfile.Character.Incognito(null)} already exists, removing..."); Profiles.Remove(existingProfile); _event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, existingProfile, null); } @@ -375,7 +385,7 @@ public partial class ProfileManager : IDisposable if (!Profiles.Remove(profile)) throw new ProfileNotFoundException(); - _logger.Debug($"Removed temporary profile for {profile.CharacterName}"); + _logger.Debug($"Removed temporary profile for {profile.Character.Incognito(null)}"); _event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, profile, null); } @@ -394,7 +404,7 @@ public partial class ProfileManager : IDisposable if (!actor.Identifier(_actorManager, out var identifier)) throw new ActorNotFoundException(); - var profile = Profiles.FirstOrDefault(x => x.TemporaryActor == identifier && x.IsTemporary); + var profile = Profiles.FirstOrDefault(x => x.Character == identifier && x.IsTemporary); if (profile == null) throw new ProfileNotFoundException(); @@ -402,17 +412,16 @@ public partial class ProfileManager : IDisposable } /// - /// Return profile by character name, does not return temporary profiles + /// Return profile by actor identifier, does not return temporary profiles. /// - /// - /// - /// - public Profile? GetProfileByCharacterName(string name, bool enabledOnly = false) + public Profile? GetProfileByActor(Actor actor, bool enabledOnly = false) { - if (string.IsNullOrWhiteSpace(name)) + var actorIdentifier = actor.GetIdentifier(_actorManager); + + if (!actorIdentifier.IsValid) return null; - var query = Profiles.Where(x => x.CharacterName == name); + var query = Profiles.Where(x => x.Character.Matches(actorIdentifier) && !x.IsTemporary); if (enabledOnly) query = query.Where(x => x.Enabled); @@ -447,7 +456,16 @@ public partial class ProfileManager : IDisposable if (profile == DefaultProfile) return false; - return profile.CharacterName.Text == name && + if (profile.ApplyToCurrentlyActiveCharacter) + { + if (_objectManager.IsInLobby) + return true; + + var currentPlayer = _actorManager.GetCurrentPlayer(); + return currentPlayer.IsValid && _actorManager.GetCurrentPlayer().Matches(actorIdentifier); + } + + return (profile.CharacterName.Text == name || profile.Character.Matches(actorIdentifier)) && (!profile.LimitLookupToOwnedObjects || (actorIdentifier.Type == IdentifierType.Owned && actorIdentifier.PlayerName == _actorManager.GetCurrentPlayer().PlayerName)); @@ -458,8 +476,19 @@ public partial class ProfileManager : IDisposable foreach (var profile in Profiles) { - if (IsProfileAppliesToCurrentActor(profile) && profile.Enabled) - yield return profile; + if(IsProfileAppliesToCurrentActor(profile)) + { + //todo: temp for migrations to v5 + if (!profile.Character.IsValid) + { + _logger.Warning($"No character for profile {profile}, but character has been found as: {actorIdentifier}, will set."); + profile.Character = actorIdentifier; + _saveService.QueueSave(profile); + } + + if (profile.Enabled) + yield return profile; + } } if (DefaultProfile != null && @@ -553,7 +582,7 @@ public partial class ProfileManager : IDisposable if (!Profiles.Remove(profile)) return; - _logger.Debug($"ProfileManager.OnArmatureChange: Removed unused temporary profile for {profile.CharacterName}"); + _logger.Debug($"ProfileManager.OnArmatureChange: Removed unused temporary profile for {profile.Character.Incognito(null)}"); _event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, profile, null); } diff --git a/CustomizePlus/UI/Windows/Controls/ActorAssignmentUi.cs b/CustomizePlus/UI/Windows/Controls/ActorAssignmentUi.cs index e3a2a78..0003320 100644 --- a/CustomizePlus/UI/Windows/Controls/ActorAssignmentUi.cs +++ b/CustomizePlus/UI/Windows/Controls/ActorAssignmentUi.cs @@ -1,39 +1,67 @@ -using Dalamud.Game.ClientState.Objects.Enums; -using ImGuiNET; -using OtterGui.Custom; -using Penumbra.GameData.Actors; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Gui; -using Penumbra.GameData.Interop; -using System; +using System; +using System.Collections.Frozen; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Text; using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Plugin; +using Dalamud.Plugin.Services; +using ImGuiNET; +using Lumina.Excel.GeneratedSheets; +using OtterGui.Custom; +using OtterGui.Log; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers.Bases; +using Penumbra.GameData.Gui; +using Penumbra.GameData.Structs; +using Penumbra.String; namespace CustomizePlus.UI.Windows.Controls; -public class ActorAssignmentUi : IDisposable +public class ActorAssignmentUi { private readonly ActorManager _actorManager; + private readonly DictBNpcENpc _dictBnpcEnpc; private WorldCombo _worldCombo = null!; - private NpcCombo _mountCombo = null!; - private NpcCombo _companionCombo = null!; - private NpcCombo _ornamentCombo = null!; - private NpcCombo _bnpcCombo = null!; - private NpcCombo _enpcCombo = null!; + private Penumbra.GameData.Gui.NpcCombo _mountCombo = null!; + private Penumbra.GameData.Gui.NpcCombo _companionCombo = null!; + //private BattleEventNpcCombo _npcCombo = null!; + private Penumbra.GameData.Gui.NpcCombo _npcCombo = null!; private bool _ready; private string _newCharacterName = string.Empty; private ObjectKind _newKind = ObjectKind.BattleNpc; - public ActorAssignmentUi(ActorManager actorManager) + /* public string CharacterName { get => _newCharacterName; } + public WorldId SelectedWorld { get => _worldCombo.CurrentSelection.Key; } + */ + public ActorIdentifier NpcIdentifier { get; private set; } = ActorIdentifier.Invalid; + public ActorIdentifier PlayerIdentifier { get; private set; } = ActorIdentifier.Invalid; + public ActorIdentifier RetainerIdentifier { get; private set; } = ActorIdentifier.Invalid; + public ActorIdentifier MannequinIdentifier { get; private set; } = ActorIdentifier.Invalid; + + public bool CanSetPlayer + => PlayerIdentifier.IsValid; + + public bool CanSetRetainer + => RetainerIdentifier.IsValid; + + public bool CanSetMannequin + => MannequinIdentifier.IsValid; + + public bool CanSetNpc + => NpcIdentifier.IsValid; + + public ActorAssignmentUi(ActorManager actorManager, DictBNpcENpc dictBnpcEnpc) { _actorManager = actorManager; + _dictBnpcEnpc = dictBnpcEnpc; - _actorManager.Awaiter.ContinueWith(_ => SetupCombos(), TaskScheduler.Default); + _actorManager.Awaiter.ContinueWith(_ => dictBnpcEnpc.Awaiter.ContinueWith(_ => SetupCombos(), TaskScheduler.Default), TaskScheduler.Default); } public void DrawWorldCombo(float width) @@ -64,6 +92,12 @@ public class ActorAssignmentUi : IDisposable if (!_ready) return; + /* if(_newKind == ObjectKind.BattleNpc || _newKind == ObjectKind.EventNpc) + { + if (_npcCombo.Draw(width)) + UpdateIdentifiersInternal(); + }*/ + var combo = GetNpcCombo(_newKind); if (combo.Draw(width)) UpdateIdentifiersInternal(); @@ -75,17 +109,15 @@ public class ActorAssignmentUi : IDisposable ObjectKind.EventNpc, ObjectKind.Companion, ObjectKind.MountType, - ObjectKind.Ornament, }; - private NpcCombo GetNpcCombo(ObjectKind kind) + private Penumbra.GameData.Gui.NpcCombo GetNpcCombo(ObjectKind kind) => kind switch { - ObjectKind.BattleNpc => _bnpcCombo, - ObjectKind.EventNpc => _enpcCombo, + ObjectKind.BattleNpc => _npcCombo, + ObjectKind.EventNpc => _npcCombo, ObjectKind.MountType => _mountCombo, ObjectKind.Companion => _companionCombo, - ObjectKind.Ornament => _ornamentCombo, _ => throw new NotImplementedException(), }; @@ -93,63 +125,57 @@ public class ActorAssignmentUi : IDisposable private void SetupCombos() { _worldCombo = new WorldCombo(_actorManager.Data.Worlds, Plugin.Logger); - _mountCombo = new NpcCombo("##mountCombo", _actorManager.Data.Mounts, Plugin.Logger); - _companionCombo = new NpcCombo("##companionCombo", _actorManager.Data.Companions, Plugin.Logger); - _ornamentCombo = new NpcCombo("##ornamentCombo", _actorManager.Data.Ornaments, Plugin.Logger); - _bnpcCombo = new NpcCombo("##bnpcCombo", _actorManager.Data.BNpcs, Plugin.Logger); - _enpcCombo = new NpcCombo("##enpcCombo", _actorManager.Data.ENpcs, Plugin.Logger); + _mountCombo = new Penumbra.GameData.Gui.NpcCombo("##mountCombo", _actorManager.Data.Mounts, Plugin.Logger); + _companionCombo = new Penumbra.GameData.Gui.NpcCombo("##companionCombo", _actorManager.Data.Companions, Plugin.Logger); + //_bnpcCombo = new Penumbra.GameData.Gui.NpcCombo("##bnpcCombo", _actorManager.Data.BNpcs, Plugin.Logger); + //_enpcCombo = new Penumbra.GameData.Gui.NpcCombo("##enpcCombo", _actorManager.Data.ENpcs, Plugin.Logger); + _npcCombo = new Penumbra.GameData.Gui.NpcCombo("##npcCombo", _dictBnpcEnpc, Plugin.Logger); _ready = true; } private void UpdateIdentifiersInternal() { - /* var combo = GetNpcCombo(_newKind); - PlayerTooltip = _collectionManager.Active.Individuals.CanAdd(IdentifierType.Player, _newCharacterName, - _worldCombo.CurrentSelection.Key, ObjectKind.None, [], out _playerIdentifiers) switch + if (ByteString.FromString(_newCharacterName, out var byteName)) { - _ when _newCharacterName.Length == 0 => NewPlayerTooltipEmpty, - IndividualCollections.AddResult.Invalid => NewPlayerTooltipInvalid, - IndividualCollections.AddResult.AlreadySet => AlreadyAssigned, - _ => string.Empty, - }; - RetainerTooltip = - _collectionManager.Active.Individuals.CanAdd(IdentifierType.Retainer, _newCharacterName, 0, ObjectKind.None, [], - out _retainerIdentifiers) switch - { - _ when _newCharacterName.Length == 0 => NewRetainerTooltipEmpty, - IndividualCollections.AddResult.Invalid => NewRetainerTooltipInvalid, - IndividualCollections.AddResult.AlreadySet => AlreadyAssigned, - _ => string.Empty, - }; - if (combo.CurrentSelection.Ids != null) - { - NpcTooltip = _collectionManager.Active.Individuals.CanAdd(IdentifierType.Npc, string.Empty, ushort.MaxValue, _newKind, - combo.CurrentSelection.Ids, out _npcIdentifiers) switch - { - IndividualCollections.AddResult.AlreadySet => AlreadyAssigned, - _ => string.Empty, - }; - OwnedTooltip = _collectionManager.Active.Individuals.CanAdd(IdentifierType.Owned, _newCharacterName, - _worldCombo.CurrentSelection.Key, _newKind, - combo.CurrentSelection.Ids, out _ownedIdentifiers) switch - { - _ when _newCharacterName.Length == 0 => NewPlayerTooltipEmpty, - IndividualCollections.AddResult.Invalid => NewPlayerTooltipInvalid, - IndividualCollections.AddResult.AlreadySet => AlreadyAssigned, - _ => string.Empty, - }; + PlayerIdentifier = _actorManager.CreatePlayer(byteName, _worldCombo.CurrentSelection.Key); + RetainerIdentifier = _actorManager.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell); + MannequinIdentifier = _actorManager.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin); } - else - { - NpcTooltip = NewNpcTooltipEmpty; - OwnedTooltip = NewNpcTooltipEmpty; - _npcIdentifiers = []; - _ownedIdentifiers = []; - }*/ - } - - public void Dispose() - { - //throw new NotImplementedException(); } } + +//Todo: Temp +/// A dictionary that matches BNpcNameId to names. +public sealed class DictBNpcENpc(IDalamudPluginInterface pluginInterface, Logger log, IDataManager gameData) + : NameDictionary(pluginInterface, log, gameData, "BNpcsENpcs", 7, () => CreateData(gameData)) +{ + /// Create the data. + private static IReadOnlyDictionary CreateData(IDataManager gameData) + { + + var sheet = gameData.GetExcelSheet(gameData.Language)!; + var sheet2 = gameData.GetExcelSheet(gameData.Language)!; + + var dict = new Dictionary((int)sheet.RowCount + (int)sheet2.RowCount); + + foreach (var n in sheet.Where(n => n.Singular.RawData.Length > 0)) + dict.TryAdd(n.RowId, DataUtility.ToTitleCaseExtended(n.Singular, n.Article)); + foreach (var n in sheet2.Where(e => e.Singular.RawData.Length > 0)) + dict.TryAdd(n.RowId, DataUtility.ToTitleCaseExtended(n.Singular, n.Article)); + + return dict.ToFrozenDictionary(); + } + + /// + public bool ContainsKey(BNpcNameId key) + => Value.ContainsKey(key.Id); + + /// + public bool TryGetValue(BNpcNameId key, [NotNullWhen(true)] out string? value) + => Value.TryGetValue(key.Id, out value); + + /// + public string this[BNpcNameId key] + => Value[key.Id]; +} + diff --git a/CustomizePlus/UI/Windows/MainWindow/Tabs/Profiles/ProfileFileSystemSelector.cs b/CustomizePlus/UI/Windows/MainWindow/Tabs/Profiles/ProfileFileSystemSelector.cs index 90ae124..9f28bdd 100644 --- a/CustomizePlus/UI/Windows/MainWindow/Tabs/Profiles/ProfileFileSystemSelector.cs +++ b/CustomizePlus/UI/Windows/MainWindow/Tabs/Profiles/ProfileFileSystemSelector.cs @@ -132,7 +132,7 @@ public class ProfileFileSystemSelector : FileSystemSelector