Update everything except for IPC and temp collections to new system.

This commit is contained in:
Ottermandias 2022-11-17 18:17:23 +01:00
parent 6a6eac1c3b
commit 4309ae8ce2
16 changed files with 400 additions and 249 deletions

View file

@ -37,7 +37,8 @@ public readonly struct ActorIdentifier : IEquatable<ActorIdentifier>
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<ActorIdentifier>
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;
}

View file

@ -14,40 +14,41 @@ public partial class ActorManager
/// </summary>
/// <param name="data">A parsed JObject</param>
/// <returns>ActorIdentifier.Invalid if the JObject can not be converted, a valid ActorIdentifier otherwise.</returns>
public ActorIdentifier FromJson(JObject data)
public ActorIdentifier FromJson(JObject? data)
{
var type = data[nameof(ActorIdentifier.Type)]?.Value<IdentifierType>() ?? IdentifierType.Invalid;
if (data == null)
return ActorIdentifier.Invalid;
var type = data[nameof(ActorIdentifier.Type)]?.ToObject<IdentifierType>() ?? IdentifierType.Invalid;
switch (type)
{
case IdentifierType.Player:
{
var name = ByteString.FromStringUnsafe(data[nameof(ActorIdentifier.PlayerName)]?.Value<string>(), false);
var homeWorld = data[nameof(ActorIdentifier.HomeWorld)]?.Value<ushort>() ?? 0;
var name = ByteString.FromStringUnsafe(data[nameof(ActorIdentifier.PlayerName)]?.ToObject<string>(), false);
var homeWorld = data[nameof(ActorIdentifier.HomeWorld)]?.ToObject<ushort>() ?? 0;
return CreatePlayer(name, homeWorld);
}
case IdentifierType.Owned:
{
var name = ByteString.FromStringUnsafe(data[nameof(ActorIdentifier.PlayerName)]?.Value<string>(), false);
var homeWorld = data[nameof(ActorIdentifier.HomeWorld)]?.Value<ushort>() ?? 0;
var kind = data[nameof(ActorIdentifier.Kind)]?.Value<ObjectKind>() ?? ObjectKind.CardStand;
var dataId = data[nameof(ActorIdentifier.DataId)]?.Value<uint>() ?? 0;
var name = ByteString.FromStringUnsafe(data[nameof(ActorIdentifier.PlayerName)]?.ToObject<string>(), false);
var homeWorld = data[nameof(ActorIdentifier.HomeWorld)]?.ToObject<ushort>() ?? 0;
var kind = data[nameof(ActorIdentifier.Kind)]?.ToObject<ObjectKind>() ?? ObjectKind.CardStand;
var dataId = data[nameof(ActorIdentifier.DataId)]?.ToObject<uint>() ?? 0;
return CreateOwned(name, homeWorld, kind, dataId);
}
case IdentifierType.Special:
{
var special = data[nameof(ActorIdentifier.Special)]?.Value<SpecialActor>() ?? 0;
var special = data[nameof(ActorIdentifier.Special)]?.ToObject<SpecialActor>() ?? 0;
return CreateSpecial(special);
}
case IdentifierType.Npc:
{
var index = data[nameof(ActorIdentifier.Index)]?.Value<ushort>() ?? ushort.MaxValue;
var kind = data[nameof(ActorIdentifier.Kind)]?.Value<ObjectKind>() ?? ObjectKind.CardStand;
var dataId = data[nameof(ActorIdentifier.DataId)]?.Value<uint>() ?? 0;
var index = data[nameof(ActorIdentifier.Index)]?.ToObject<ushort>() ?? ushort.MaxValue;
var kind = data[nameof(ActorIdentifier.Kind)]?.ToObject<ObjectKind>() ?? ObjectKind.CardStand;
var dataId = data[nameof(ActorIdentifier.DataId)]?.ToObject<uint>() ?? 0;
return CreateNpc(kind, dataId, index);
}
case IdentifierType.Invalid:
default:
return ActorIdentifier.Invalid;
default: return ActorIdentifier.Invalid;
}
}

View file

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

View file

@ -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<Dictionary<string, string>>() ?? new Dictionary<string, string>();
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();
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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