mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Update everything except for IPC and temp collections to new system.
This commit is contained in:
parent
6a6eac1c3b
commit
4309ae8ce2
16 changed files with 400 additions and 249 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ) );
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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." );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ) )
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
|
|
|
|||
|
|
@ -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 ) )
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue