From 4309ae8ce267489c7ebd9c9032909aca4feee8a6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 17 Nov 2022 18:17:23 +0100 Subject: [PATCH] Update everything except for IPC and temp collections to new system. --- Penumbra.GameData/Actors/ActorIdentifier.cs | 8 +- .../Actors/ActorManager.Identifiers.cs | 31 +-- Penumbra/Api/PenumbraApi.cs | 21 +- .../Collections/CollectionManager.Active.cs | 205 ++++++++---------- Penumbra/Collections/CollectionManager.cs | 17 +- .../IndividualCollections.Access.cs | 22 +- .../IndividualCollections.Files.cs | 78 ++++++- Penumbra/Collections/IndividualCollections.cs | 87 ++++---- Penumbra/Configuration.Migration.cs | 44 +++- .../Resolver/PathResolver.DrawObjectState.cs | 5 +- Penumbra/Penumbra.cs | 56 +++-- Penumbra/UI/Classes/ModFileSystemSelector.cs | 5 +- .../ConfigWindow.CollectionsTab.Individual.cs | 33 ++- Penumbra/UI/ConfigWindow.CollectionsTab.cs | 14 +- Penumbra/UI/ConfigWindow.Misc.cs | 21 +- Penumbra/UI/ConfigWindow.ModsTab.cs | 2 +- 16 files changed, 400 insertions(+), 249 deletions(-) diff --git a/Penumbra.GameData/Actors/ActorIdentifier.cs b/Penumbra.GameData/Actors/ActorIdentifier.cs index 0903c312..74e6d5f1 100644 --- a/Penumbra.GameData/Actors/ActorIdentifier.cs +++ b/Penumbra.GameData/Actors/ActorIdentifier.cs @@ -37,7 +37,8 @@ public readonly struct ActorIdentifier : IEquatable IdentifierType.Player => HomeWorld == other.HomeWorld && PlayerName.EqualsCi(other.PlayerName), IdentifierType.Owned => HomeWorld == other.HomeWorld && PlayerName.EqualsCi(other.PlayerName) && Manager.DataIdEquals(this, other), IdentifierType.Special => Special == other.Special, - IdentifierType.Npc => Manager.DataIdEquals(this, other) && (Index == other.Index || Index == ushort.MaxValue || other.Index == ushort.MaxValue), + IdentifierType.Npc => Manager.DataIdEquals(this, other) + && (Index == other.Index || Index == ushort.MaxValue || other.Index == ushort.MaxValue), _ => false, }; } @@ -107,8 +108,9 @@ public readonly struct ActorIdentifier : IEquatable ret.Add(nameof(Special), Special.ToString()); return ret; case IdentifierType.Npc: - ret.Add(nameof(Kind), Kind.ToString()); - ret.Add(nameof(Index), Index); + ret.Add(nameof(Kind), Kind.ToString()); + if (Index != ushort.MaxValue) + ret.Add(nameof(Index), Index); ret.Add(nameof(DataId), DataId); return ret; } diff --git a/Penumbra.GameData/Actors/ActorManager.Identifiers.cs b/Penumbra.GameData/Actors/ActorManager.Identifiers.cs index ca5731f5..ec0782fb 100644 --- a/Penumbra.GameData/Actors/ActorManager.Identifiers.cs +++ b/Penumbra.GameData/Actors/ActorManager.Identifiers.cs @@ -14,40 +14,41 @@ public partial class ActorManager /// /// A parsed JObject /// ActorIdentifier.Invalid if the JObject can not be converted, a valid ActorIdentifier otherwise. - public ActorIdentifier FromJson(JObject data) + public ActorIdentifier FromJson(JObject? data) { - var type = data[nameof(ActorIdentifier.Type)]?.Value() ?? IdentifierType.Invalid; + if (data == null) + return ActorIdentifier.Invalid; + + var type = data[nameof(ActorIdentifier.Type)]?.ToObject() ?? IdentifierType.Invalid; switch (type) { case IdentifierType.Player: { - var name = ByteString.FromStringUnsafe(data[nameof(ActorIdentifier.PlayerName)]?.Value(), false); - var homeWorld = data[nameof(ActorIdentifier.HomeWorld)]?.Value() ?? 0; + var name = ByteString.FromStringUnsafe(data[nameof(ActorIdentifier.PlayerName)]?.ToObject(), false); + var homeWorld = data[nameof(ActorIdentifier.HomeWorld)]?.ToObject() ?? 0; return CreatePlayer(name, homeWorld); } case IdentifierType.Owned: { - var name = ByteString.FromStringUnsafe(data[nameof(ActorIdentifier.PlayerName)]?.Value(), false); - var homeWorld = data[nameof(ActorIdentifier.HomeWorld)]?.Value() ?? 0; - var kind = data[nameof(ActorIdentifier.Kind)]?.Value() ?? ObjectKind.CardStand; - var dataId = data[nameof(ActorIdentifier.DataId)]?.Value() ?? 0; + var name = ByteString.FromStringUnsafe(data[nameof(ActorIdentifier.PlayerName)]?.ToObject(), false); + var homeWorld = data[nameof(ActorIdentifier.HomeWorld)]?.ToObject() ?? 0; + var kind = data[nameof(ActorIdentifier.Kind)]?.ToObject() ?? ObjectKind.CardStand; + var dataId = data[nameof(ActorIdentifier.DataId)]?.ToObject() ?? 0; return CreateOwned(name, homeWorld, kind, dataId); } case IdentifierType.Special: { - var special = data[nameof(ActorIdentifier.Special)]?.Value() ?? 0; + var special = data[nameof(ActorIdentifier.Special)]?.ToObject() ?? 0; return CreateSpecial(special); } case IdentifierType.Npc: { - var index = data[nameof(ActorIdentifier.Index)]?.Value() ?? ushort.MaxValue; - var kind = data[nameof(ActorIdentifier.Kind)]?.Value() ?? ObjectKind.CardStand; - var dataId = data[nameof(ActorIdentifier.DataId)]?.Value() ?? 0; + var index = data[nameof(ActorIdentifier.Index)]?.ToObject() ?? ushort.MaxValue; + var kind = data[nameof(ActorIdentifier.Kind)]?.ToObject() ?? ObjectKind.CardStand; + var dataId = data[nameof(ActorIdentifier.DataId)]?.ToObject() ?? 0; return CreateNpc(kind, dataId, index); } - case IdentifierType.Invalid: - default: - return ActorIdentifier.Invalid; + default: return ActorIdentifier.Invalid; } } diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index 8cc799f1..3521369c 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -14,6 +14,8 @@ using System.IO; using System.Linq; using System.Reflection; using Penumbra.Api.Enums; +using Penumbra.GameData.Actors; +using Penumbra.String; using Penumbra.String.Classes; namespace Penumbra.Api; @@ -212,7 +214,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi { CheckInitialized(); return ResolvePath( path, Penumbra.ModManager, - Penumbra.CollectionManager.Character( characterName ) ); + Penumbra.CollectionManager.Individual( NameToIdentifier( characterName ) ) ); } public string[] ReverseResolvePath( string path, string characterName ) @@ -223,7 +225,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi return new[] { path }; } - var ret = Penumbra.CollectionManager.Character( characterName ).ReverseResolvePath( new FullPath( path ) ); + var ret = Penumbra.CollectionManager.Individual( NameToIdentifier( characterName ) ).ReverseResolvePath( new FullPath( path ) ); return ret.Select( r => r.ToString() ).ToArray(); } @@ -297,7 +299,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi public (string, bool) GetCharacterCollection( string characterName ) { CheckInitialized(); - return Penumbra.CollectionManager.Characters.TryGetValue( characterName, out var collection ) + return Penumbra.CollectionManager.Individuals.TryGetCollection( NameToIdentifier( characterName ), out var collection ) ? ( collection.Name, true ) : ( Penumbra.CollectionManager.Default.Name, false ); } @@ -570,7 +572,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi return ( PenumbraApiEc.InvalidArgument, string.Empty ); } - if( !forceOverwriteCharacter && Penumbra.CollectionManager.Characters.ContainsKey( character ) + if( !forceOverwriteCharacter && Penumbra.CollectionManager.Individuals.Individuals.ContainsKey( NameToIdentifier(character) ) || Penumbra.TempMods.Collections.ContainsKey( character ) ) { return ( PenumbraApiEc.CharacterCollectionExists, string.Empty ); @@ -680,7 +682,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi CheckInitialized(); var collection = Penumbra.TempMods.Collections.TryGetValue( characterName, out var c ) ? c - : Penumbra.CollectionManager.Character( characterName ); + : Penumbra.CollectionManager.Individual( NameToIdentifier(characterName) ); var set = collection.MetaCache?.Manipulations.ToArray() ?? Array.Empty< MetaManipulation >(); return Functions.ToCompressedBase64( set, MetaManipulation.CurrentVersion ); } @@ -804,7 +806,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi c.ModSettingChanged += Del; } - private void SubscribeToNewCollections( CollectionType type, ModCollection? oldCollection, ModCollection? newCollection, string? _ ) + private void SubscribeToNewCollections( CollectionType type, ModCollection? oldCollection, ModCollection? newCollection, string _ ) { if( type != CollectionType.Inactive ) { @@ -827,4 +829,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi public void InvokePostSettingsPanel( string modDirectory ) => PostSettingsPanelDraw?.Invoke( modDirectory ); + + // TODO + private static ActorIdentifier NameToIdentifier( string name ) + { + var b = ByteString.FromStringUnsafe( name, false ); + return Penumbra.Actors.CreatePlayer( b, ( ushort )( Dalamud.ClientState.LocalPlayer?.HomeWorld.Id ?? ushort.MaxValue ) ); + } } \ No newline at end of file diff --git a/Penumbra/Collections/CollectionManager.Active.cs b/Penumbra/Collections/CollectionManager.Active.cs index 47977d08..e30dcece 100644 --- a/Penumbra/Collections/CollectionManager.Active.cs +++ b/Penumbra/Collections/CollectionManager.Active.cs @@ -8,6 +8,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Dalamud.Interface.Internal.Notifications; +using Penumbra.GameData.Actors; +using Penumbra.Util; namespace Penumbra.Collections; @@ -36,21 +39,20 @@ public partial class ModCollection private ModCollection DefaultName { get; set; } = Empty; // The list of character collections. - private readonly Dictionary< string, ModCollection > _characters = new(); - public readonly IndividualCollections Individuals = new(Penumbra.Actors); + public readonly IndividualCollections Individuals = new(Penumbra.Actors); - public IReadOnlyDictionary< string, ModCollection > Characters - => _characters; - - // If a name does not correspond to a character, return the default collection instead. - public ModCollection Character( string name ) - => _characters.TryGetValue( name, out var c ) ? c : Default; + public ModCollection Individual( ActorIdentifier identifier ) + => Individuals.TryGetCollection( identifier, out var c ) ? c : Default; // Special Collections private readonly ModCollection?[] _specialCollections = new ModCollection?[Enum.GetValues< CollectionType >().Length - 4]; // Return the configured collection for the given type or null. - public ModCollection? ByType( CollectionType type, string? name = null ) + // Does not handle Inactive, use ByName instead. + public ModCollection? ByType( CollectionType type ) + => ByType( type, ActorIdentifier.Invalid ); + + public ModCollection? ByType( CollectionType type, ActorIdentifier identifier ) { if( type.IsSpecial() ) { @@ -59,28 +61,23 @@ public partial class ModCollection return type switch { - CollectionType.Default => Default, - CollectionType.Interface => Interface, - CollectionType.Current => Current, - CollectionType.Individual => name != null ? _characters.TryGetValue( name, out var c ) ? c : null : null, - CollectionType.Inactive => name != null ? ByName( name, out var c ) ? c : null : null, - _ => null, + CollectionType.Default => Default, + CollectionType.Interface => Interface, + CollectionType.Current => Current, + CollectionType.Individual => identifier.IsValid ? Individuals.TryGetCollection( identifier, out var c ) ? c : null : null, + _ => null, }; } - // Set a active collection, can be used to set Default, Current or Character collections. - private void SetCollection( int newIdx, CollectionType collectionType, string? characterName = null ) + // Set a active collection, can be used to set Default, Current, Interface, Special, or Individual collections. + private void SetCollection( int newIdx, CollectionType collectionType, int individualIndex = -1 ) { var oldCollectionIdx = collectionType switch { - CollectionType.Default => Default.Index, - CollectionType.Interface => Interface.Index, - CollectionType.Current => Current.Index, - CollectionType.Individual => characterName?.Length > 0 - ? _characters.TryGetValue( characterName, out var c ) - ? c.Index - : Default.Index - : -1, + CollectionType.Default => Default.Index, + CollectionType.Interface => Interface.Index, + CollectionType.Current => Current.Index, + CollectionType.Individual => individualIndex < 0 || individualIndex >= Individuals.Count ? -1 : Individuals[ individualIndex ].Collection.Index, _ when collectionType.IsSpecial() => _specialCollections[ ( int )collectionType ]?.Index ?? Default.Index, _ => -1, }; @@ -114,7 +111,12 @@ public partial class ModCollection Current = newCollection; break; case CollectionType.Individual: - _characters[ characterName! ] = newCollection; + if( !Individuals.ChangeCollection( individualIndex, newCollection ) ) + { + RemoveCache( newIdx ); + return; + } + break; default: _specialCollections[ ( int )collectionType ] = newCollection; @@ -124,7 +126,7 @@ public partial class ModCollection RemoveCache( oldCollectionIdx ); UpdateCurrentCollectionInUse(); - CollectionChanged.Invoke( collectionType, this[ oldCollectionIdx ], newCollection, characterName ); + CollectionChanged.Invoke( collectionType, this[ oldCollectionIdx ], newCollection, Individuals[ individualIndex ].DisplayName ); } private void UpdateCurrentCollectionInUse() @@ -132,11 +134,11 @@ public partial class ModCollection .OfType< ModCollection >() .Prepend( Interface ) .Prepend( Default ) - .Concat( Characters.Values ) + .Concat( Individuals.Assignments.Select( kvp => kvp.Collection ) ) .SelectMany( c => c.GetFlattenedInheritance() ).Contains( Current ); - public void SetCollection( ModCollection collection, CollectionType collectionType, string? characterName = null ) - => SetCollection( collection.Index, collectionType, characterName ); + public void SetCollection( ModCollection collection, CollectionType collectionType, int individualIndex = -1 ) + => SetCollection( collection.Index, collectionType, individualIndex ); // Create a special collection if it does not exist and set it to Empty. public bool CreateSpecialCollection( CollectionType collectionType ) @@ -147,7 +149,7 @@ public partial class ModCollection } _specialCollections[ ( int )collectionType ] = Default; - CollectionChanged.Invoke( collectionType, null, Default, null ); + CollectionChanged.Invoke( collectionType, null, Default ); return true; } @@ -163,31 +165,38 @@ public partial class ModCollection if( old != null ) { _specialCollections[ ( int )collectionType ] = null; - CollectionChanged.Invoke( collectionType, old, null, null ); + CollectionChanged.Invoke( collectionType, old, null ); } } - // Create a new character collection. Returns false if the character name already has a collection. - public bool CreateCharacterCollection( string characterName ) + // Wrappers around Individual Collection handling. + public void CreateIndividualCollection( ActorIdentifier[] identifiers ) { - if( _characters.ContainsKey( characterName ) ) + if( Individuals.Add( identifiers, Default ) ) { - return false; + CollectionChanged.Invoke( CollectionType.Individual, null, Default, Individuals.Last().DisplayName ); } - - _characters[ characterName ] = Default; - CollectionChanged.Invoke( CollectionType.Individual, null, Default, characterName ); - return true; } - // Remove a character collection if it exists. - public void RemoveCharacterCollection( string characterName ) + public void RemoveIndividualCollection( int individualIndex ) { - if( _characters.TryGetValue( characterName, out var collection ) ) + if( individualIndex < 0 || individualIndex >= Individuals.Count ) { - RemoveCache( collection.Index ); - _characters.Remove( characterName ); - CollectionChanged.Invoke( CollectionType.Individual, collection, null, characterName ); + return; + } + + var (name, old) = Individuals[ individualIndex ]; + if( Individuals.Delete( individualIndex ) ) + { + CollectionChanged.Invoke( CollectionType.Individual, old, null, name ); + } + } + + public void MoveIndividualCollection( int from, int to ) + { + if( Individuals.Move( from, to ) ) + { + SaveActiveCollections(); } } @@ -209,7 +218,8 @@ public partial class ModCollection var defaultIdx = GetIndexForCollectionName( defaultName ); if( defaultIdx < 0 ) { - Penumbra.Log.Error( $"Last choice of {ConfigWindow.DefaultCollection} {defaultName} is not available, reset to {Empty.Name}." ); + ChatUtil.NotificationMessage( $"Last choice of {ConfigWindow.DefaultCollection} {defaultName} is not available, reset to {Empty.Name}.", "Load Failure", + NotificationType.Warning ); Default = Empty; configChanged = true; } @@ -223,8 +233,8 @@ public partial class ModCollection var interfaceIdx = GetIndexForCollectionName( interfaceName ); if( interfaceIdx < 0 ) { - Penumbra.Log.Error( - $"Last choice of {ConfigWindow.InterfaceCollection} {interfaceName} is not available, reset to {Empty.Name}." ); + ChatUtil.NotificationMessage( + $"Last choice of {ConfigWindow.InterfaceCollection} {interfaceName} is not available, reset to {Empty.Name}.", "Load Failure", NotificationType.Warning ); Interface = Empty; configChanged = true; } @@ -238,8 +248,8 @@ public partial class ModCollection var currentIdx = GetIndexForCollectionName( currentName ); if( currentIdx < 0 ) { - Penumbra.Log.Error( - $"Last choice of {ConfigWindow.SelectedCollection} {currentName} is not available, reset to {DefaultCollection}." ); + ChatUtil.NotificationMessage( + $"Last choice of {ConfigWindow.SelectedCollection} {currentName} is not available, reset to {DefaultCollection}.", "Load Failure", NotificationType.Warning ); Current = DefaultName; configChanged = true; } @@ -257,7 +267,7 @@ public partial class ModCollection var idx = GetIndexForCollectionName( typeName ); if( idx < 0 ) { - Penumbra.Log.Error( $"Last choice of {name} Collection {typeName} is not available, removed." ); + ChatUtil.NotificationMessage( $"Last choice of {name} Collection {typeName} is not available, removed.", "Load Failure", NotificationType.Warning ); configChanged = true; } else @@ -267,30 +277,14 @@ public partial class ModCollection } } - // Load character collections. If a player name comes up multiple times, the last one is applied. - var characters = jObject[ nameof( Characters ) ]?.ToObject< Dictionary< string, string > >() ?? new Dictionary< string, string >(); - foreach( var (player, collectionName) in characters ) - { - var idx = GetIndexForCollectionName( collectionName ); - if( idx < 0 ) - { - Penumbra.Log.Error( $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {Empty.Name}." ); - _characters.Add( player, Empty ); - configChanged = true; - } - else - { - _characters.Add( player, this[ idx ] ); - } - } + configChanged |= MigrateIndividualCollections( jObject ); + configChanged |= Individuals.ReadJObject( jObject[ nameof( Individuals ) ] as JArray, this ); // Save any changes and create all required caches. if( configChanged ) { SaveActiveCollections(); } - - MigrateIndividualCollections( jObject ); } // Migrate ungendered collections to Male and Female for 0.5.9.0. @@ -323,21 +317,24 @@ public partial class ModCollection } // Migrate individual collections to Identifiers for 0.6.0. - private bool MigrateIndividualCollections(JObject jObject) + private bool MigrateIndividualCollections( JObject jObject ) { var version = jObject[ nameof( Version ) ]?.Value< int >() ?? 0; if( version > 0 ) + { return false; - + } + // Load character collections. If a player name comes up multiple times, the last one is applied. - var characters = jObject[nameof( Characters )]?.ToObject>() ?? new Dictionary(); - var dict = new Dictionary< string, ModCollection >( characters.Count ); + var characters = jObject[ "Characters" ]?.ToObject< Dictionary< string, string > >() ?? new Dictionary< string, string >(); + var dict = new Dictionary< string, ModCollection >( characters.Count ); foreach( var (player, collectionName) in characters ) { var idx = GetIndexForCollectionName( collectionName ); if( idx < 0 ) { - Penumbra.Log.Error( $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {Empty.Name}." ); + ChatUtil.NotificationMessage( $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {Empty.Name}.", "Load Failure", + NotificationType.Warning ); dict.Add( player, Empty ); } else @@ -345,7 +342,7 @@ public partial class ModCollection dict.Add( player, this[ idx ] ); } } - + Individuals.Migrate0To1( dict ); return true; } @@ -353,46 +350,32 @@ public partial class ModCollection public void SaveActiveCollections() { Penumbra.Framework.RegisterDelayed( nameof( SaveActiveCollections ), - () => SaveActiveCollections( Default.Name, Interface.Name, Current.Name, - Characters.Select( kvp => ( kvp.Key, kvp.Value.Name ) ), - _specialCollections.WithIndex() - .Where( c => c.Item1 != null ) - .Select( c => ( ( CollectionType )c.Item2, c.Item1!.Name ) ) ) ); + SaveActiveCollectionsInternal ); } - internal static void SaveActiveCollections( string def, string ui, string current, IEnumerable< (string, string) > characters, - IEnumerable< (CollectionType, string) > special ) + internal void SaveActiveCollectionsInternal() { var file = ActiveCollectionFile; try { + var jObj = new JObject + { + { nameof( Version ), Version }, + { nameof( Default ), Default.Name }, + { nameof( Interface ), Interface.Name }, + { nameof( Current ), Current.Name }, + }; + foreach( var (type, collection) in _specialCollections.WithIndex().Where( p => p.Value != null ).Select( p => ( ( CollectionType )p.Index, p.Value! ) ) ) + { + jObj.Add( type.ToString(), collection.Name ); + } + + jObj.Add( nameof( Individuals ), Individuals.ToJObject() ); using var stream = File.Open( file, File.Exists( file ) ? FileMode.Truncate : FileMode.CreateNew ); using var writer = new StreamWriter( stream ); - using var j = new JsonTextWriter( writer ); - j.Formatting = Formatting.Indented; - j.WriteStartObject(); - j.WritePropertyName( nameof( Default ) ); - j.WriteValue( def ); - j.WritePropertyName( nameof( Interface ) ); - j.WriteValue( ui ); - j.WritePropertyName( nameof( Current ) ); - j.WriteValue( current ); - foreach( var (type, collection) in special ) - { - j.WritePropertyName( type.ToString() ); - j.WriteValue( collection ); - } - - j.WritePropertyName( nameof( Characters ) ); - j.WriteStartObject(); - foreach( var (character, collection) in characters ) - { - j.WritePropertyName( character, true ); - j.WriteValue( collection ); - } - - j.WriteEndObject(); - j.WriteEndObject(); + using var j = new JsonTextWriter( writer ) + { Formatting = Formatting.Indented }; + jObj.WriteTo( j ); Penumbra.Log.Verbose( "Active Collections saved." ); } catch( Exception e ) @@ -424,7 +407,7 @@ public partial class ModCollection } // Save if any of the active collections is changed. - private void SaveOnChange( CollectionType collectionType, ModCollection? _1, ModCollection? _2, string? _3 ) + private void SaveOnChange( CollectionType collectionType, ModCollection? _1, ModCollection? _2, string _3 ) { if( collectionType != CollectionType.Inactive ) { @@ -437,7 +420,7 @@ public partial class ModCollection public void CreateNecessaryCaches() { var tasks = _specialCollections.OfType< ModCollection >() - .Concat( _characters.Values ) + .Concat( Individuals.Select( p => p.Collection ) ) .Prepend( Current ) .Prepend( Default ) .Prepend( Interface ) @@ -455,7 +438,7 @@ public partial class ModCollection && idx != Interface.Index && idx != Current.Index && _specialCollections.All( c => c == null || c.Index != idx ) - && _characters.Values.All( c => c.Index != idx ) ) + && Individuals.Select( p => p.Collection ).All( c => c.Index != idx ) ) { _collections[ idx ].ClearCache(); } diff --git a/Penumbra/Collections/CollectionManager.cs b/Penumbra/Collections/CollectionManager.cs index 05dc797f..66db1d1d 100644 --- a/Penumbra/Collections/CollectionManager.cs +++ b/Penumbra/Collections/CollectionManager.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using Penumbra.GameData.Actors; namespace Penumbra.Collections; @@ -15,9 +16,9 @@ public partial class ModCollection public sealed partial class Manager : IDisposable, IEnumerable< ModCollection > { // On addition, oldCollection is null. On deletion, newCollection is null. - // CharacterName is only set for type == Character. + // displayName is only set for type == Individual. public delegate void CollectionChangeDelegate( CollectionType collectionType, ModCollection? oldCollection, - ModCollection? newCollection, string? characterName = null ); + ModCollection? newCollection, string displayName = "" ); private readonly Mod.Manager _modManager; @@ -139,19 +140,21 @@ public partial class ModCollection if( idx == Current.Index ) { - SetCollection( DefaultName, CollectionType.Current ); + SetCollection( DefaultName.Index, CollectionType.Current ); } if( idx == Default.Index ) { - SetCollection( Empty, CollectionType.Default ); + SetCollection( Empty.Index, CollectionType.Default ); } - foreach( var (characterName, _) in _characters.Where( c => c.Value.Index == idx ).ToList() ) + for( var i = 0; i < Individuals.Count; ++i ) { - SetCollection( Empty, CollectionType.Individual, characterName ); + if( Individuals[ i ].Collection.Index == idx ) + { + Individuals.ChangeCollection( i, Empty ); + } } - var collection = _collections[ idx ]; // Clear own inheritances. diff --git a/Penumbra/Collections/IndividualCollections.Access.cs b/Penumbra/Collections/IndividualCollections.Access.cs index f6d927ad..29c403d7 100644 --- a/Penumbra/Collections/IndividualCollections.Access.cs +++ b/Penumbra/Collections/IndividualCollections.Access.cs @@ -12,7 +12,7 @@ namespace Penumbra.Collections; public sealed partial class IndividualCollections : IReadOnlyList< (string DisplayName, ModCollection Collection) > { public IEnumerator< (string DisplayName, ModCollection Collection) > GetEnumerator() - => _assignments.Select( kvp => ( kvp.Key, kvp.Value.Collection ) ).GetEnumerator(); + => _assignments.Select( t => ( t.DisplayName, t.Collection ) ).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -21,7 +21,7 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ => _assignments.Count; public (string DisplayName, ModCollection Collection) this[ int index ] - => ( _assignments.Keys[ index ], _assignments.Values[ index ].Collection ); + => ( _assignments[ index ].DisplayName, _assignments[ index ].Collection ); public bool TryGetCollection( ActorIdentifier identifier, [NotNullWhen( true )] out ModCollection? collection ) { @@ -36,7 +36,7 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ } // Handle generic NPC - var npcIdentifier = _manager.CreateIndividualUnchecked( IdentifierType.Npc, ByteString.Empty, ushort.MaxValue, identifier.Kind, identifier.DataId ); + var npcIdentifier = _actorManager.CreateIndividualUnchecked( IdentifierType.Npc, ByteString.Empty, ushort.MaxValue, identifier.Kind, identifier.DataId ); if( npcIdentifier.IsValid && _individuals.TryGetValue( npcIdentifier, out collection ) ) { return true; @@ -45,7 +45,7 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ // Handle Ownership. if( Penumbra.Config.UseOwnerNameForCharacterCollection ) { - identifier = _manager.CreateIndividualUnchecked( IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld, ObjectKind.None, uint.MaxValue ); + identifier = _actorManager.CreateIndividualUnchecked( IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld, ObjectKind.None, uint.MaxValue ); return CheckWorlds( identifier, out collection ); } @@ -59,12 +59,12 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ case SpecialActor.FittingRoom when Penumbra.Config.UseCharacterCollectionInTryOn: case SpecialActor.DyePreview when Penumbra.Config.UseCharacterCollectionInTryOn: case SpecialActor.Portrait when Penumbra.Config.UseCharacterCollectionsInCards: - return CheckWorlds( _manager.GetCurrentPlayer(), out collection ); + return CheckWorlds( _actorManager.GetCurrentPlayer(), out collection ); case SpecialActor.ExamineScreen: { - return CheckWorlds( _manager.GetInspectPlayer(), out collection! ) - || CheckWorlds( _manager.GetCardPlayer(), out collection! ) - || CheckWorlds( _manager.GetGlamourPlayer(), out collection! ); + return CheckWorlds( _actorManager.GetInspectPlayer(), out collection! ) + || CheckWorlds( _actorManager.GetCardPlayer(), out collection! ) + || CheckWorlds( _actorManager.GetGlamourPlayer(), out collection! ); } } @@ -76,10 +76,10 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ } public bool TryGetCollection( GameObject? gameObject, out ModCollection? collection ) - => TryGetCollection( _manager.FromObject( gameObject ), out collection ); + => TryGetCollection( _actorManager.FromObject( gameObject ), out collection ); public unsafe bool TryGetCollection( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* gameObject, out ModCollection? collection ) - => TryGetCollection( _manager.FromObject( gameObject ), out collection ); + => TryGetCollection( _actorManager.FromObject( gameObject ), out collection ); private bool CheckWorlds( ActorIdentifier identifier, out ModCollection? collection ) { @@ -94,7 +94,7 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ return true; } - identifier = _manager.CreateIndividualUnchecked( identifier.Type, identifier.PlayerName, ushort.MaxValue, identifier.Kind, identifier.DataId ); + identifier = _actorManager.CreateIndividualUnchecked( identifier.Type, identifier.PlayerName, ushort.MaxValue, identifier.Kind, identifier.DataId ); if( identifier.IsValid && _individuals.TryGetValue( identifier, out collection ) ) { return true; diff --git a/Penumbra/Collections/IndividualCollections.Files.cs b/Penumbra/Collections/IndividualCollections.Files.cs index 8c0997f0..16581e9c 100644 --- a/Penumbra/Collections/IndividualCollections.Files.cs +++ b/Penumbra/Collections/IndividualCollections.Files.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Interface.Internal.Notifications; +using Newtonsoft.Json.Linq; using Penumbra.GameData.Actors; using Penumbra.String; using Penumbra.Util; @@ -11,7 +12,66 @@ namespace Penumbra.Collections; public partial class IndividualCollections { - public const int Version = 1; + public JArray ToJObject() + { + var ret = new JArray(); + foreach( var (name, identifiers, collection) in Assignments ) + { + var tmp = identifiers[0].ToJson(); + tmp.Add( "Collection", collection.Name ); + tmp.Add( "Display", name ); + ret.Add( tmp ); + } + + return ret; + } + + public bool ReadJObject( JArray? obj, ModCollection.Manager manager ) + { + if( obj == null ) + { + return true; + } + + var changes = false; + foreach( var data in obj ) + { + try + { + var identifier = Penumbra.Actors.FromJson( data as JObject ); + var group = GetGroup( identifier ); + if( group.Length == 0 || group.Any( i => !i.IsValid ) ) + { + changes = true; + ChatUtil.NotificationMessage( "Could not load an unknown individual collection, removed.", "Load Failure", NotificationType.Warning ); + continue; + } + + var collectionName = data[ "Collection" ]?.ToObject< string >() ?? string.Empty; + if( collectionName.Length == 0 || !manager.ByName( collectionName, out var collection ) ) + { + changes = true; + ChatUtil.NotificationMessage( $"Could not load the collection \"{collectionName}\" as individual collection for {identifier}, set to None.", "Load Failure", + NotificationType.Warning ); + continue; + } + + if( !Add( group, collection ) ) + { + changes = true; + ChatUtil.NotificationMessage( $"Could not add an individual collection for {identifier}, removed.", "Load Failure", + NotificationType.Warning ); + } + } + catch( Exception e ) + { + changes = true; + ChatUtil.NotificationMessage( $"Could not load an unknown individual collection, removed:\n{e}", "Load Failure", NotificationType.Error ); + } + } + + return changes; + } internal void Migrate0To1( Dictionary< string, ModCollection > old ) { @@ -28,30 +88,30 @@ public partial class IndividualCollections var kind = ObjectKind.None; var lowerName = name.ToLowerInvariant(); // Prefer matching NPC names, fewer false positives than preferring players. - if( FindDataId( lowerName, _manager.Companions, out var dataId ) ) + if( FindDataId( lowerName, _actorManager.Companions, out var dataId ) ) { kind = ObjectKind.Companion; } - else if( FindDataId( lowerName, _manager.Mounts, out dataId ) ) + else if( FindDataId( lowerName, _actorManager.Mounts, out dataId ) ) { kind = ObjectKind.MountType; } - else if( FindDataId( lowerName, _manager.BNpcs, out dataId ) ) + else if( FindDataId( lowerName, _actorManager.BNpcs, out dataId ) ) { kind = ObjectKind.BattleNpc; } - else if( FindDataId( lowerName, _manager.ENpcs, out dataId ) ) + else if( FindDataId( lowerName, _actorManager.ENpcs, out dataId ) ) { kind = ObjectKind.EventNpc; } - var identifier = _manager.CreateNpc( kind, dataId ); + var identifier = _actorManager.CreateNpc( kind, dataId ); if( identifier.IsValid ) { // If the name corresponds to a valid npc, add it as a group. If this fails, notify users. var group = GetGroup( identifier ); var ids = string.Join( ", ", group.Select( i => i.DataId.ToString() ) ); - if( Add( $"{_manager.ToName( kind, dataId )} ({kind.ToName()})", group, collection ) ) + if( Add( $"{_actorManager.ToName( kind, dataId )} ({kind.ToName()})", group, collection ) ) { Penumbra.Log.Information( $"Migrated {name} ({kind.ToName()}) to NPC Identifiers [{ids}]." ); } @@ -65,10 +125,10 @@ public partial class IndividualCollections // If it is not a valid NPC name, check if it can be a player name. else if( ActorManager.VerifyPlayerName( name ) ) { - identifier = _manager.CreatePlayer( ByteString.FromStringUnsafe( name, false ), ushort.MaxValue ); + identifier = _actorManager.CreatePlayer( ByteString.FromStringUnsafe( name, false ), ushort.MaxValue ); var shortName = string.Join( " ", name.Split().Select( n => $"{n[ 0 ]}." ) ); // Try to migrate the player name without logging full names. - if( Add( $"{name} ({_manager.ToWorldName( identifier.HomeWorld )})", new[] { identifier }, collection ) ) + if( Add( $"{name} ({_actorManager.ToWorldName( identifier.HomeWorld )})", new[] { identifier }, collection ) ) { Penumbra.Log.Information( $"Migrated {shortName} ({collection.AnonymizedName}) to Player Identifier." ); } diff --git a/Penumbra/Collections/IndividualCollections.cs b/Penumbra/Collections/IndividualCollections.cs index fc4a83b2..a8239eb5 100644 --- a/Penumbra/Collections/IndividualCollections.cs +++ b/Penumbra/Collections/IndividualCollections.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Dalamud.Game.ClientState.Objects.Enums; +using OtterGui.Filesystem; using Penumbra.GameData.Actors; using Penumbra.String; @@ -9,18 +10,18 @@ namespace Penumbra.Collections; public sealed partial class IndividualCollections { - private readonly ActorManager _manager; - private readonly SortedList< string, (IReadOnlyList< ActorIdentifier > Identifiers, ModCollection Collection) > _assignments = new(); - private readonly Dictionary< ActorIdentifier, ModCollection > _individuals = new(); + private readonly ActorManager _actorManager; + private readonly List< (string DisplayName, IReadOnlyList< ActorIdentifier > Identifiers, ModCollection Collection) > _assignments = new(); + private readonly Dictionary< ActorIdentifier, ModCollection > _individuals = new(); - public IReadOnlyDictionary< string, (IReadOnlyList< ActorIdentifier > Identifiers, ModCollection Collection) > Assignments + public IReadOnlyList< (string DisplayName, IReadOnlyList< ActorIdentifier > Identifiers, ModCollection Collection) > Assignments => _assignments; public IReadOnlyDictionary< ActorIdentifier, ModCollection > Individuals => _individuals; - public IndividualCollections( ActorManager manager ) - => _manager = manager; + public IndividualCollections( ActorManager actorManager ) + => _actorManager = actorManager; public enum AddResult { @@ -61,7 +62,7 @@ public sealed partial class IndividualCollections return AddResult.Invalid; } - var identifier = _manager.CreatePlayer( playerName, homeWorld ); + var identifier = _actorManager.CreatePlayer( playerName, homeWorld ); identifiers = new[] { identifier }; break; case IdentifierType.Owned: @@ -70,10 +71,10 @@ public sealed partial class IndividualCollections return AddResult.Invalid; } - identifiers = dataIds.Select( id => _manager.CreateOwned( ownerName, homeWorld, kind, id ) ).ToArray(); + identifiers = dataIds.Select( id => _actorManager.CreateOwned( ownerName, homeWorld, kind, id ) ).ToArray(); break; case IdentifierType.Npc: - identifiers = dataIds.Select( id => _manager.CreateIndividual( IdentifierType.Npc, ByteString.Empty, ushort.MaxValue, kind, id ) ).ToArray(); + identifiers = dataIds.Select( id => _actorManager.CreateIndividual( IdentifierType.Npc, ByteString.Empty, ushort.MaxValue, kind, id ) ).ToArray(); break; default: identifiers = Array.Empty< ActorIdentifier >(); @@ -109,38 +110,33 @@ public sealed partial class IndividualCollections { IdentifierType.Player => new[] { identifier.CreatePermanent() }, IdentifierType.Special => new[] { identifier }, - IdentifierType.Owned => CreateNpcs( _manager, identifier.CreatePermanent() ), - IdentifierType.Npc => CreateNpcs( _manager, identifier ), + IdentifierType.Owned => CreateNpcs( _actorManager, identifier.CreatePermanent() ), + IdentifierType.Npc => CreateNpcs( _actorManager, identifier ), _ => Array.Empty< ActorIdentifier >(), }; } - public bool Add( ActorIdentifier[] identifiers, ModCollection collection ) + internal bool Add( ActorIdentifier[] identifiers, ModCollection collection ) { if( identifiers.Length == 0 || !identifiers[ 0 ].IsValid ) { return false; } - var name = identifiers[ 0 ].Type switch - { - IdentifierType.Player => $"{identifiers[ 0 ].PlayerName} ({_manager.ToWorldName( identifiers[ 0 ].HomeWorld )})", - IdentifierType.Owned => - $"{identifiers[ 0 ].PlayerName} ({_manager.ToWorldName( identifiers[ 0 ].HomeWorld )})'s {_manager.ToName( identifiers[ 0 ].Kind, identifiers[ 0 ].DataId )}", - IdentifierType.Npc => $"{_manager.ToName( identifiers[ 0 ].Kind, identifiers[ 0 ].DataId )} ({identifiers[ 0 ].Kind})", - _ => string.Empty, - }; + var name = DisplayString( identifiers[ 0 ] ); return Add( name, identifiers, collection ); } private bool Add( string displayName, ActorIdentifier[] identifiers, ModCollection collection ) { - if( CanAdd( identifiers ) != AddResult.Valid || displayName.Length == 0 || _assignments.ContainsKey( displayName ) ) + if( CanAdd( identifiers ) != AddResult.Valid + || displayName.Length == 0 + || _assignments.Any( a => a.DisplayName.Equals( displayName, StringComparison.OrdinalIgnoreCase ) ) ) { return false; } - _assignments.Add( displayName, ( identifiers, collection ) ); + _assignments.Add( ( displayName, identifiers, collection ) ); foreach( var identifier in identifiers ) { _individuals.Add( identifier, collection ); @@ -149,21 +145,21 @@ public sealed partial class IndividualCollections return true; } - public bool ChangeCollection( string displayName, ModCollection newCollection ) - { - var displayIndex = _assignments.IndexOfKey( displayName ); - return ChangeCollection( displayIndex, newCollection ); - } + internal bool ChangeCollection( ActorIdentifier identifier, ModCollection newCollection ) + => ChangeCollection( DisplayString( identifier ), newCollection ); - public bool ChangeCollection( int displayIndex, ModCollection newCollection ) + internal bool ChangeCollection( string displayName, ModCollection newCollection ) + => ChangeCollection( _assignments.FindIndex( t => t.DisplayName.Equals( displayName, StringComparison.OrdinalIgnoreCase ) ), newCollection ); + + internal bool ChangeCollection( int displayIndex, ModCollection newCollection ) { - if( displayIndex < 0 || displayIndex >= _assignments.Count || _assignments.Values[ displayIndex ].Collection == newCollection ) + if( displayIndex < 0 || displayIndex >= _assignments.Count || _assignments[ displayIndex ].Collection == newCollection ) { return false; } - _assignments.Values[ displayIndex ] = _assignments.Values[ displayIndex ] with { Collection = newCollection }; - foreach( var identifier in _assignments.Values[ displayIndex ].Identifiers ) + _assignments[ displayIndex ] = _assignments[ displayIndex ] with { Collection = newCollection }; + foreach( var identifier in _assignments[ displayIndex ].Identifiers ) { _individuals[ identifier ] = newCollection; } @@ -171,20 +167,20 @@ public sealed partial class IndividualCollections return true; } - public bool Delete( string displayName ) - { - var displayIndex = _assignments.IndexOfKey( displayName ); - return Delete( displayIndex ); - } + internal bool Delete( ActorIdentifier identifier ) + => Delete( DisplayString( identifier ) ); - public bool Delete( int displayIndex ) + internal bool Delete( string displayName ) + => Delete( _assignments.FindIndex( t => t.DisplayName.Equals( displayName, StringComparison.OrdinalIgnoreCase ) ) ); + + internal bool Delete( int displayIndex ) { if( displayIndex < 0 || displayIndex >= _assignments.Count ) { return false; } - var (identifiers, _) = _assignments.Values[ displayIndex ]; + var (name, identifiers, _) = _assignments[ displayIndex ]; _assignments.RemoveAt( displayIndex ); foreach( var identifier in identifiers ) { @@ -193,4 +189,19 @@ public sealed partial class IndividualCollections return true; } + + internal bool Move( int from, int to ) + => _assignments.Move( from, to ); + + private string DisplayString( ActorIdentifier identifier ) + { + return identifier.Type switch + { + IdentifierType.Player => $"{identifier.PlayerName} ({_actorManager.ToWorldName( identifier.HomeWorld )})", + IdentifierType.Owned => + $"{identifier.PlayerName} ({_actorManager.ToWorldName( identifier.HomeWorld )})'s {_actorManager.ToName( identifier.Kind, identifier.DataId )}", + IdentifierType.Npc => $"{_actorManager.ToName( identifier.Kind, identifier.DataId )} ({identifier.Kind.ToName()})", + _ => string.Empty, + }; + } } \ No newline at end of file diff --git a/Penumbra/Configuration.Migration.cs b/Penumbra/Configuration.Migration.cs index 20df5d04..ee1ee6ac 100644 --- a/Penumbra/Configuration.Migration.cs +++ b/Penumbra/Configuration.Migration.cs @@ -224,10 +224,52 @@ public partial class Configuration CurrentCollection = _data[ nameof( CurrentCollection ) ]?.ToObject< string >() ?? CurrentCollection; DefaultCollection = _data[ nameof( DefaultCollection ) ]?.ToObject< string >() ?? DefaultCollection; CharacterCollections = _data[ nameof( CharacterCollections ) ]?.ToObject< Dictionary< string, string > >() ?? CharacterCollections; - ModCollection.Manager.SaveActiveCollections( DefaultCollection, CurrentCollection, DefaultCollection, + SaveActiveCollectionsV0( DefaultCollection, CurrentCollection, DefaultCollection, CharacterCollections.Select( kvp => ( kvp.Key, kvp.Value ) ), Array.Empty< (CollectionType, string) >() ); } + // Outdated saving using the Characters list. + private static void SaveActiveCollectionsV0( string def, string ui, string current, IEnumerable<(string, string)> characters, + IEnumerable<(CollectionType, string)> special ) + { + var file = ModCollection.Manager.ActiveCollectionFile; + try + { + using var stream = File.Open( file, File.Exists( file ) ? FileMode.Truncate : FileMode.CreateNew ); + using var writer = new StreamWriter( stream ); + using var j = new JsonTextWriter( writer ); + j.Formatting = Formatting.Indented; + j.WriteStartObject(); + j.WritePropertyName( nameof( ModCollection.Manager.Default ) ); + j.WriteValue( def ); + j.WritePropertyName( nameof( ModCollection.Manager.Interface ) ); + j.WriteValue( ui ); + j.WritePropertyName( nameof( ModCollection.Manager.Current ) ); + j.WriteValue( current ); + foreach( var (type, collection) in special ) + { + j.WritePropertyName( type.ToString() ); + j.WriteValue( collection ); + } + + j.WritePropertyName( "Characters" ); + j.WriteStartObject(); + foreach( var (character, collection) in characters ) + { + j.WritePropertyName( character, true ); + j.WriteValue( collection ); + } + + j.WriteEndObject(); + j.WriteEndObject(); + Penumbra.Log.Verbose( "Active Collections saved." ); + } + catch( Exception e ) + { + Penumbra.Log.Error( $"Could not save active collections to file {file}:\n{e}" ); + } + } + // Collections were introduced and the previous CurrentCollection got put into ModDirectory. private void Version0To1() { diff --git a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs index 3742a51e..3f53c089 100644 --- a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs +++ b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs @@ -8,6 +8,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object; using Penumbra.Api; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Classes; +using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.String.Classes; @@ -221,9 +222,9 @@ public unsafe partial class PathResolver } // Update collections linked to Game/DrawObjects due to a change in collection configuration. - private void CheckCollections( CollectionType type, ModCollection? _1, ModCollection? _2, string? name ) + private void CheckCollections( CollectionType type, ModCollection? _1, ModCollection? _2, string _3 ) { - if( type is CollectionType.Inactive or CollectionType.Current ) + if( type is CollectionType.Inactive or CollectionType.Current or CollectionType.Interface ) { return; } diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 91561644..b7f45c4b 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -355,26 +355,27 @@ public class Penumbra : IDalamudPlugin return false; } - var oldCollection = CollectionManager.ByType( type, characterName ); - if( collection == oldCollection ) - { - Dalamud.Chat.Print( $"{collection.Name} already is the {type.ToName()} Collection." ); - return false; - } - - if( oldCollection == null ) - { - if( type.IsSpecial() ) - { - CollectionManager.CreateSpecialCollection( type ); - } - else if( type is CollectionType.Individual ) - { - CollectionManager.CreateCharacterCollection( characterName! ); - } - } - - CollectionManager.SetCollection( collection, type, characterName ); + // TODO + //var oldCollection = CollectionManager.ByType( type, characterName ); + //if( collection == oldCollection ) + //{ + // Dalamud.Chat.Print( $"{collection.Name} already is the {type.ToName()} Collection." ); + // return false; + //} + // + //if( oldCollection == null ) + //{ + // if( type.IsSpecial() ) + // { + // CollectionManager.CreateSpecialCollection( type ); + // } + // else if( type is CollectionType.Individual ) + // { + // CollectionManager.CreateIndividualCollection( characterName! ); + // } + //} + // + //CollectionManager.SetCollection( collection, type, characterName ); Dalamud.Chat.Print( $"Set {collection.Name} as {type.ToName()} Collection{( characterName != null ? $" for {characterName}." : "." )}" ); return true; } @@ -507,8 +508,15 @@ public class Penumbra : IDalamudPlugin ModManager.Sum( m => m.TotalManipulations ) ); sb.AppendFormat( "> **`IMC Exceptions Thrown: `** {0}\n", ImcExceptions ); - string CharacterName( string name ) - => string.Join( " ", name.Split().Select( n => $"{n[ 0 ]}." ) ) + ':'; + string CharacterName( ActorIdentifier id, string name ) + { + if( id.Type is IdentifierType.Player or IdentifierType.Owned ) + { + return string.Join( " ", name.Split( ' ', 3 ).Select( n => $"{n[ 0 ]}." ) ) + ':'; + } + + return name + ':'; + } void PrintCollection( ModCollection c ) => sb.AppendFormat( "**Collection {0}**\n" @@ -535,9 +543,9 @@ public class Penumbra : IDalamudPlugin } } - foreach( var (name, collection) in CollectionManager.Characters ) + foreach( var (name, id, collection) in CollectionManager.Individuals.Assignments ) { - sb.AppendFormat( "> **`{1,-29}`** {0}\n", collection.AnonymizedName, CharacterName( name ) ); + sb.AppendFormat( "> **`{1,-29}`** {0}\n", collection.AnonymizedName, CharacterName( id[ 0 ], name ) ); } foreach( var collection in CollectionManager.Where( c => c.HasCache ) ) diff --git a/Penumbra/UI/Classes/ModFileSystemSelector.cs b/Penumbra/UI/Classes/ModFileSystemSelector.cs index 10311595..ac8beade 100644 --- a/Penumbra/UI/Classes/ModFileSystemSelector.cs +++ b/Penumbra/UI/Classes/ModFileSystemSelector.cs @@ -14,6 +14,7 @@ using System.IO; using System.Linq; using System.Numerics; using Penumbra.Api.Enums; +using Penumbra.GameData.Actors; namespace Penumbra.UI.Classes; @@ -47,7 +48,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod Penumbra.ModManager.ModDataChanged += OnModDataChange; Penumbra.ModManager.ModDiscoveryStarted += StoreCurrentSelection; Penumbra.ModManager.ModDiscoveryFinished += RestoreLastSelection; - OnCollectionChange( CollectionType.Current, null, Penumbra.CollectionManager.Current, null ); + OnCollectionChange( CollectionType.Current, null, Penumbra.CollectionManager.Current, "" ); } public override void Dispose() @@ -377,7 +378,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod OnSelectionChange( Selected, Selected, default ); } - private void OnCollectionChange( CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection, string? _ ) + private void OnCollectionChange( CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection, string _ ) { if( collectionType != CollectionType.Current || oldCollection == newCollection ) { diff --git a/Penumbra/UI/ConfigWindow.CollectionsTab.Individual.cs b/Penumbra/UI/ConfigWindow.CollectionsTab.Individual.cs index a7a0f65b..9737969d 100644 --- a/Penumbra/UI/ConfigWindow.CollectionsTab.Individual.cs +++ b/Penumbra/UI/ConfigWindow.CollectionsTab.Individual.cs @@ -11,7 +11,6 @@ using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Interface.Components; using OtterGui.Widgets; using Penumbra.GameData.Actors; -using Lumina.Data.Parsing; namespace Penumbra.UI; @@ -110,6 +109,7 @@ public partial class ConfigWindow return ret; } + private int _individualDragDropIdx = -1; private void DrawIndividualAssignments() { @@ -124,19 +124,42 @@ public partial class ConfigWindow ImGui.Separator(); for( var i = 0; i < Penumbra.CollectionManager.Individuals.Count; ++i ) { - var (name, collection) = Penumbra.CollectionManager.Individuals[ i ]; + var (name, _) = Penumbra.CollectionManager.Individuals[ i ]; using var id = ImRaii.PushId( i ); - DrawCollectionSelector( string.Empty, _window._inputTextWidth.X, CollectionType.Individual, true, name ); + CollectionsWithEmpty.Draw( string.Empty, _window._inputTextWidth.X, i ); ImGui.SameLine(); if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), _window._iconButtonSize, string.Empty, false, true ) ) { - Penumbra.CollectionManager.Individuals.Delete( i ); + Penumbra.CollectionManager.RemoveIndividualCollection( i ); } ImGui.SameLine(); ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted( name ); + ImGui.Selectable( name ); + using( var source = ImRaii.DragDropSource() ) + { + if( source ) + { + ImGui.SetDragDropPayload( "Individual", IntPtr.Zero, 0 ); + _individualDragDropIdx = i; + } + } + + using( var target = ImRaii.DragDropTarget() ) + { + if( !target.Success || !ImGuiUtil.IsDropping( "Individual" ) ) + { + continue; + } + + if( _individualDragDropIdx >= 0 ) + { + Penumbra.CollectionManager.MoveIndividualCollection( _individualDragDropIdx, i ); + } + + _individualDragDropIdx = -1; + } } ImGui.Dummy( Vector2.Zero ); diff --git a/Penumbra/UI/ConfigWindow.CollectionsTab.cs b/Penumbra/UI/ConfigWindow.CollectionsTab.cs index 1fd968c5..aa6b6a4e 100644 --- a/Penumbra/UI/ConfigWindow.CollectionsTab.cs +++ b/Penumbra/UI/ConfigWindow.CollectionsTab.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Components; @@ -41,7 +39,7 @@ public partial class ConfigWindow // Input text fields. private string _newCollectionName = string.Empty; - private bool _canAddCollection = false; + private bool _canAddCollection; // Create a new collection that is either empty or a duplicate of the current collection. // Resets the new collection name. @@ -103,7 +101,7 @@ public partial class ConfigWindow private void DrawCurrentCollectionSelector( Vector2 width ) { using var group = ImRaii.Group(); - DrawCollectionSelector( "##current", _window._inputTextWidth.X, CollectionType.Current, false, null ); + DrawCollectionSelector( "##current", _window._inputTextWidth.X, CollectionType.Current, false ); ImGui.SameLine(); ImGuiUtil.LabeledHelpMarker( SelectedCollection, "This collection will be modified when using the Installed Mods tab and making changes.\nIt is not automatically assigned to anything." ); @@ -126,7 +124,7 @@ public partial class ConfigWindow private void DrawDefaultCollectionSelector() { using var group = ImRaii.Group(); - DrawCollectionSelector( "##default", _window._inputTextWidth.X, CollectionType.Default, true, null ); + DrawCollectionSelector( "##default", _window._inputTextWidth.X, CollectionType.Default, true ); ImGui.SameLine(); ImGuiUtil.LabeledHelpMarker( DefaultCollection, $"Mods in the {DefaultCollection} are loaded for anything that is not associated with the user interface or a character in the game," @@ -136,7 +134,7 @@ public partial class ConfigWindow private void DrawInterfaceCollectionSelector() { using var group = ImRaii.Group(); - DrawCollectionSelector( "##interface", _window._inputTextWidth.X, CollectionType.Interface, true, null ); + DrawCollectionSelector( "##interface", _window._inputTextWidth.X, CollectionType.Interface, true ); ImGui.SameLine(); ImGuiUtil.LabeledHelpMarker( InterfaceCollection, $"Mods in the {InterfaceCollection} are loaded for any file that the game categorizes as an UI file. This is mostly icons as well as the tiles that generate the user interface windows themselves." ); @@ -147,7 +145,7 @@ public partial class ConfigWindow public (CollectionType, string, string)? CurrentType => CollectionTypeExtensions.Special[ CurrentIdx ]; - public int CurrentIdx = 0; + public int CurrentIdx; private readonly float _unscaledWidth; private readonly string _label; @@ -219,7 +217,7 @@ public partial class ConfigWindow if( collection != null ) { using var id = ImRaii.PushId( ( int )type ); - DrawCollectionSelector( string.Empty, _window._inputTextWidth.X, type, true, null ); + DrawCollectionSelector( string.Empty, _window._inputTextWidth.X, type, true ); ImGui.SameLine(); if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), _window._iconButtonSize, string.Empty, false, true ) ) diff --git a/Penumbra/UI/ConfigWindow.Misc.cs b/Penumbra/UI/ConfigWindow.Misc.cs index 1207fa57..394f78fe 100644 --- a/Penumbra/UI/ConfigWindow.Misc.cs +++ b/Penumbra/UI/ConfigWindow.Misc.cs @@ -11,6 +11,7 @@ using OtterGui.Raii; using OtterGui.Widgets; using Penumbra.Api.Enums; using Penumbra.Collections; +using Penumbra.GameData.Actors; using Penumbra.Interop.Structs; using Penumbra.String; using Penumbra.UI.Classes; @@ -98,12 +99,21 @@ public partial class ConfigWindow : base( items ) { } - public void Draw( string label, float width, CollectionType type, string? characterName ) + public void Draw( string label, float width, int individualIdx ) { - var current = Penumbra.CollectionManager.ByType( type, characterName ); + var (_, collection) = Penumbra.CollectionManager.Individuals[ individualIdx ]; + if( Draw( label, collection.Name, width, ImGui.GetTextLineHeightWithSpacing() ) && CurrentSelection != null ) + { + Penumbra.CollectionManager.SetCollection( CurrentSelection, CollectionType.Individual, individualIdx ); + } + } + + public void Draw( string label, float width, CollectionType type ) + { + var current = Penumbra.CollectionManager.ByType( type, ActorIdentifier.Invalid ); if( Draw( label, current?.Name ?? string.Empty, width, ImGui.GetTextLineHeightWithSpacing() ) && CurrentSelection != null ) { - Penumbra.CollectionManager.SetCollection( CurrentSelection, type, characterName ); + Penumbra.CollectionManager.SetCollection( CurrentSelection, type ); } } @@ -115,9 +125,8 @@ public partial class ConfigWindow private static readonly CollectionSelector Collections = new(Penumbra.CollectionManager.OrderBy( c => c.Name )); // Draw a collection selector of a certain width for a certain type. - private static void DrawCollectionSelector( string label, float width, CollectionType collectionType, bool withEmpty, - string? characterName ) - => ( withEmpty ? CollectionsWithEmpty : Collections ).Draw( label, width, collectionType, characterName ); + private static void DrawCollectionSelector( string label, float width, CollectionType collectionType, bool withEmpty ) + => ( withEmpty ? CollectionsWithEmpty : Collections ).Draw( label, width, collectionType ); // Set up the file selector with the right flags and custom side bar items. public static FileDialogManager SetupFileManager() diff --git a/Penumbra/UI/ConfigWindow.ModsTab.cs b/Penumbra/UI/ConfigWindow.ModsTab.cs index eb3a3a8a..b520dd91 100644 --- a/Penumbra/UI/ConfigWindow.ModsTab.cs +++ b/Penumbra/UI/ConfigWindow.ModsTab.cs @@ -136,7 +136,7 @@ public partial class ConfigWindow ImGui.SameLine(); DrawInheritedCollectionButton( 3 * buttonSize ); ImGui.SameLine(); - DrawCollectionSelector( "##collectionSelector", 2 * buttonSize.X, CollectionType.Current, false, null ); + DrawCollectionSelector( "##collectionSelector", 2 * buttonSize.X, CollectionType.Current, false ); } OpenTutorial( BasicTutorialSteps.CollectionSelectors );