diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index ace2b9f4..94e921de 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -210,14 +210,22 @@ public class PenumbraApi : IDisposable, IPenumbraApi return ResolvePath( path, Penumbra.ModManager, PathResolver.PlayerCollection() ); } + // TODO: cleanup when incrementing API level public string ResolvePath( string path, string characterName ) + => ResolvePath( path, characterName, ushort.MaxValue ); + + public string ResolvePath( string path, string characterName, ushort worldId ) { CheckInitialized(); return ResolvePath( path, Penumbra.ModManager, - Penumbra.CollectionManager.Individual( NameToIdentifier( characterName ) ) ); + Penumbra.CollectionManager.Individual( NameToIdentifier( characterName, worldId ) ) ); } + // TODO: cleanup when incrementing API level public string[] ReverseResolvePath( string path, string characterName ) + => ReverseResolvePath( path, characterName, ushort.MaxValue ); + + public string[] ReverseResolvePath( string path, string characterName, ushort worldId ) { CheckInitialized(); if( !Penumbra.Config.EnableMods ) @@ -225,7 +233,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi return new[] { path }; } - var ret = Penumbra.CollectionManager.Individual( NameToIdentifier( characterName ) ).ReverseResolvePath( new FullPath( path ) ); + var ret = Penumbra.CollectionManager.Individual( NameToIdentifier( characterName, worldId ) ).ReverseResolvePath( new FullPath( path ) ); return ret.Select( r => r.ToString() ).ToArray(); } @@ -296,10 +304,14 @@ public class PenumbraApi : IDisposable, IPenumbraApi return Penumbra.CollectionManager.Interface.Name; } + // TODO: cleanup when incrementing API level public (string, bool) GetCharacterCollection( string characterName ) + => GetCharacterCollection( characterName, ushort.MaxValue ); + + public (string, bool) GetCharacterCollection( string characterName, ushort worldId ) { CheckInitialized(); - return Penumbra.CollectionManager.Individuals.TryGetCollection( NameToIdentifier( characterName ), out var collection ) + return Penumbra.CollectionManager.Individuals.TryGetCollection( NameToIdentifier( characterName, worldId ), out var collection ) ? ( collection.Name, true ) : ( Penumbra.CollectionManager.Default.Name, false ); } @@ -371,7 +383,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi public PenumbraApiEc AddMod( string modDirectory ) { CheckInitialized(); - var dir = new DirectoryInfo( Path.Join( Penumbra.ModManager.BasePath.FullName, Path.GetFileName(modDirectory) ) ); + var dir = new DirectoryInfo( Path.Join( Penumbra.ModManager.BasePath.FullName, Path.GetFileName( modDirectory ) ) ); if( !dir.Exists ) { return PenumbraApiEc.FileMissing; @@ -564,34 +576,50 @@ public class PenumbraApi : IDisposable, IPenumbraApi } public (PenumbraApiEc, string) CreateTemporaryCollection( string tag, string character, bool forceOverwriteCharacter ) + => CreateTemporaryCollection( tag, character, forceOverwriteCharacter, ushort.MaxValue ); + + public (PenumbraApiEc, string) CreateTemporaryCollection( string tag, string character, bool forceOverwriteCharacter, ushort worldId ) { CheckInitialized(); - if( character.Length is 0 or > 32 || tag.Length == 0 ) + if( !ActorManager.VerifyPlayerName( character.AsSpan() ) || tag.Length == 0 ) { return ( PenumbraApiEc.InvalidArgument, string.Empty ); } - if( !forceOverwriteCharacter && Penumbra.CollectionManager.Individuals.Individuals.ContainsKey( NameToIdentifier( character ) ) - || Penumbra.TempMods.Collections.ContainsKey( character ) ) + var identifier = NameToIdentifier( character, worldId ); + if( !identifier.IsValid ) + { + return ( PenumbraApiEc.InvalidArgument, string.Empty ); + } + + if( !forceOverwriteCharacter && Penumbra.CollectionManager.Individuals.Individuals.ContainsKey( identifier ) + || Penumbra.TempMods.Collections.Individuals.ContainsKey( identifier ) ) { return ( PenumbraApiEc.CharacterCollectionExists, string.Empty ); } - var name = Penumbra.TempMods.SetTemporaryCollection( tag, character ); - return ( PenumbraApiEc.Success, name ); + var name = Penumbra.TempMods.CreateTemporaryCollection( tag, character ); + if( name.Length == 0 ) + { + return ( PenumbraApiEc.CharacterCollectionExists, string.Empty ); + } + + if( Penumbra.TempMods.AddIdentifier( name, identifier ) ) + { + return ( PenumbraApiEc.Success, name ); + } + + Penumbra.TempMods.RemoveTemporaryCollection( name ); + return ( PenumbraApiEc.UnknownError, string.Empty ); } public PenumbraApiEc RemoveTemporaryCollection( string character ) { CheckInitialized(); - if( !Penumbra.TempMods.Collections.ContainsKey( character ) ) - { - return PenumbraApiEc.NothingChanged; - } - - Penumbra.TempMods.RemoveTemporaryCollection( character ); - return PenumbraApiEc.Success; + return Penumbra.TempMods.RemoveByCharacterName( character ) + ? PenumbraApiEc.Success + : PenumbraApiEc.NothingChanged; } public PenumbraApiEc AddTemporaryModAll( string tag, Dictionary< string, string > paths, string manipString, int priority ) @@ -677,12 +705,17 @@ public class PenumbraApi : IDisposable, IPenumbraApi return Functions.ToCompressedBase64( set, MetaManipulation.CurrentVersion ); } + // TODO: cleanup when incrementing API public string GetMetaManipulations( string characterName ) + => GetMetaManipulations( characterName, ushort.MaxValue ); + + public string GetMetaManipulations( string characterName, ushort worldId ) { CheckInitialized(); - var collection = Penumbra.TempMods.Collections.TryGetValue( characterName, out var c ) + var identifier = NameToIdentifier( characterName, worldId ); + var collection = Penumbra.TempMods.Collections.TryGetCollection( identifier, out var c ) ? c - : Penumbra.CollectionManager.Individual( NameToIdentifier( characterName ) ); + : Penumbra.CollectionManager.Individual( identifier ); var set = collection.MetaCache?.Manipulations.ToArray() ?? Array.Empty< MetaManipulation >(); return Functions.ToCompressedBase64( set, MetaManipulation.CurrentVersion ); } @@ -830,10 +863,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi public void InvokePostSettingsPanel( string modDirectory ) => PostSettingsPanelDraw?.Invoke( modDirectory ); - // TODO - private static ActorIdentifier NameToIdentifier( string name ) + // TODO: replace all usages with ActorIdentifier stuff when incrementing API + private static ActorIdentifier NameToIdentifier( string name, ushort worldId ) { + // Verified to be valid name beforehand. var b = ByteString.FromStringUnsafe( name, false ); - return Penumbra.Actors.CreatePlayer( b, ( ushort )( Dalamud.ClientState.LocalPlayer?.HomeWorld.Id ?? ushort.MaxValue ) ); + return Penumbra.Actors.CreatePlayer( b, worldId ); } } \ No newline at end of file diff --git a/Penumbra/Api/TempModManager.cs b/Penumbra/Api/TempModManager.cs index 132b772a..db087f9d 100644 --- a/Penumbra/Api/TempModManager.cs +++ b/Penumbra/Api/TempModManager.cs @@ -1,10 +1,11 @@ -using OtterGui; using Penumbra.Collections; using Penumbra.Meta.Manipulations; using Penumbra.Mods; -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Penumbra.GameData.Actors; +using Penumbra.String; using Penumbra.String.Classes; namespace Penumbra.Api; @@ -21,7 +22,10 @@ public class TempModManager { private readonly Dictionary< ModCollection, List< Mod.TemporaryMod > > _mods = new(); private readonly List< Mod.TemporaryMod > _modsForAllCollections = new(); - private readonly Dictionary< string, ModCollection > _collections = new(); + private readonly Dictionary< string, ModCollection > _customCollections = new(); + public readonly IndividualCollections Collections = new(Penumbra.Actors); + + public event ModCollection.Manager.CollectionChangeDelegate? CollectionChanged; public IReadOnlyDictionary< ModCollection, List< Mod.TemporaryMod > > Mods => _mods; @@ -29,13 +33,11 @@ public class TempModManager public IReadOnlyList< Mod.TemporaryMod > ModsForAllCollections => _modsForAllCollections; - public IReadOnlyDictionary< string, ModCollection > Collections - => _collections; + public IReadOnlyDictionary< string, ModCollection > CustomCollections + => _customCollections; public bool CollectionByName( string name, [NotNullWhen( true )] out ModCollection? collection ) - => Collections.Values.FindFirst( c => string.Equals( c.Name, name, StringComparison.OrdinalIgnoreCase ), out collection ); - - public event ModCollection.Manager.CollectionChangeDelegate? CollectionChanged; + => _customCollections.TryGetValue( name, out collection ); // These functions to check specific redirections or meta manipulations for existence are currently unused. //public bool IsRegistered( string tag, ModCollection? collection, Utf8GamePath gamePath, out FullPath? fullPath, out int priority ) @@ -149,27 +151,87 @@ public class TempModManager return RedirectResult.Success; } - public string SetTemporaryCollection( string tag, string characterName ) + public string CreateTemporaryCollection( string tag, string customName ) { - var collection = ModCollection.CreateNewTemporary( tag, characterName ); - _collections[ characterName ] = collection; - CollectionChanged?.Invoke(CollectionType.Temporary, null, collection ); + var collection = ModCollection.CreateNewTemporary( tag, customName ); + if( _customCollections.ContainsKey( collection.Name ) ) + { + collection.ClearCache(); + return string.Empty; + } + _customCollections.Add( collection.Name, collection ); return collection.Name; } - public bool RemoveTemporaryCollection( string characterName ) + public bool RemoveTemporaryCollection( string collectionName ) { - if( _collections.Remove( characterName, out var c ) ) + if( !_customCollections.Remove( collectionName, out var collection ) ) { - _mods.Remove( c ); - c.ClearCache(); - CollectionChanged?.Invoke( CollectionType.Temporary, c, null ); + return false; + } + + _mods.Remove( collection ); + collection.ClearCache(); + for( var i = 0; i < Collections.Count; ++i ) + { + if( Collections[ i ].Collection == collection ) + { + CollectionChanged?.Invoke( CollectionType.Temporary, collection, null, Collections[ i ].DisplayName ); + Collections.Delete( i ); + } + } + + return true; + } + + public bool AddIdentifier( ModCollection collection, params ActorIdentifier[] identifiers ) + { + if( Collections.Add( identifiers, collection ) ) + { + CollectionChanged?.Invoke( CollectionType.Temporary, null, collection, Collections.Last().DisplayName ); return true; } return false; } + public bool AddIdentifier( string collectionName, params ActorIdentifier[] identifiers ) + { + if( !_customCollections.TryGetValue( collectionName, out var collection ) ) + { + return false; + } + + return AddIdentifier( collection, identifiers ); + } + + public bool AddIdentifier( string collectionName, string characterName, ushort worldId = ushort.MaxValue ) + { + if( !ByteString.FromString( characterName, out var byteString, false ) ) + { + return false; + } + + var identifier = Penumbra.Actors.CreatePlayer( byteString, worldId ); + if( !identifier.IsValid ) + { + return false; + } + + return AddIdentifier( collectionName, identifier ); + } + + internal bool RemoveByCharacterName( string characterName, ushort worldId = ushort.MaxValue ) + { + if( !ByteString.FromString( characterName, out var byteString, false ) ) + { + return false; + } + + var identifier = Penumbra.Actors.CreatePlayer( byteString, worldId ); + return Collections.Individuals.TryGetValue( identifier, out var collection ) && RemoveTemporaryCollection( collection.Name ); + } + // Apply any new changes to the temporary mod. private static void ApplyModChange( Mod.TemporaryMod mod, ModCollection? collection, bool created, bool removed ) diff --git a/Penumbra/Interop/Resolver/PathResolver.Identification.cs b/Penumbra/Interop/Resolver/PathResolver.Identification.cs index d031b153..b5ce0e41 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Identification.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Identification.cs @@ -76,7 +76,7 @@ public unsafe partial class PathResolver // Check both temporary and permanent character collections. Temporary first. private static ModCollection? CollectionByIdentifier( ActorIdentifier identifier ) - => Penumbra.TempMods.Collections.TryGetValue( identifier.ToString(), out var collection ) + => Penumbra.TempMods.Collections.TryGetCollection( identifier, out var collection ) || Penumbra.CollectionManager.Individuals.TryGetCollection( identifier, out collection ) ? collection : null;