Continued work on actor identification, migration seems to work.

This commit is contained in:
Ottermandias 2022-11-16 15:33:41 +01:00
parent 0444c28187
commit bda3c1f1ac
11 changed files with 407 additions and 115 deletions

View file

@ -37,6 +37,7 @@ public partial class ModCollection
// The list of character collections.
private readonly Dictionary< string, ModCollection > _characters = new();
public readonly IndividualCollections Individuals = new(Penumbra.Actors);
public IReadOnlyDictionary< string, ModCollection > Characters
=> _characters;
@ -288,6 +289,8 @@ public partial class ModCollection
{
SaveActiveCollections();
}
MigrateIndividualCollections( jObject );
}
// Migrate ungendered collections to Male and Female for 0.5.9.0.
@ -320,13 +323,12 @@ public partial class ModCollection
}
// Migrate individual collections to Identifiers for 0.6.0.
private bool MigrateIndividualCollections(JObject jObject, out IndividualCollections collections)
private bool MigrateIndividualCollections(JObject jObject)
{
var version = jObject[ nameof( Version ) ]?.Value< int >() ?? 0;
collections = new IndividualCollections( Penumbra.Actors );
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 );
@ -340,15 +342,14 @@ public partial class ModCollection
}
else
{
dict.Add( player, this[idx] );
dict.Add( player, this[ idx ] );
}
}
collections.Migrate0To1( dict );
Individuals.Migrate0To1( dict );
return true;
}
public void SaveActiveCollections()
{
Penumbra.Framework.RegisterDelayed( nameof( SaveActiveCollections ),

View file

@ -94,11 +94,12 @@ public enum CollectionType : byte
MaleVeenaNpc,
FemaleVeenaNpc,
Inactive, // A collection was added or removed
Default, // The default collection was changed
Interface, // The ui collection was changed
Character, // A character collection was changed
Current, // The current collection was changed
Inactive, // A collection was added or removed
Default, // The default collection was changed
Interface, // The ui collection was changed
Character, // A character collection was changed
Individual, // An Individual collection was changed
Current, // The current collection was changed
}
public static class CollectionTypeExtensions

View file

@ -0,0 +1,117 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Dalamud.Game.ClientState.Objects.Types;
using Penumbra.GameData.Actors;
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();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public int Count
=> _assignments.Count;
public (string DisplayName, ModCollection Collection) this[ int index ]
=> ( _assignments.Keys[ index ], _assignments.Values[ index ].Collection );
public bool TryGetCollection( ActorIdentifier identifier, [NotNullWhen( true )] out ModCollection? collection )
{
switch( identifier.Type )
{
case IdentifierType.Player: return CheckWorlds( identifier, out collection );
case IdentifierType.Owned:
{
if( CheckWorlds( identifier, out collection! ) )
{
return true;
}
// Handle generic NPC
var npcIdentifier = _manager.CreateNpc( identifier.Kind, identifier.DataId );
if( npcIdentifier.IsValid && _individuals.TryGetValue( identifier, out collection ) )
{
return true;
}
// Handle Ownership.
if( Penumbra.Config.UseOwnerNameForCharacterCollection )
{
identifier = _manager.CreatePlayer( identifier.PlayerName, identifier.HomeWorld );
return CheckWorlds( identifier, out collection );
}
return false;
}
case IdentifierType.Npc: return _individuals.TryGetValue( identifier, out collection );
case IdentifierType.Special:
switch( identifier.Special )
{
case SpecialActor.CharacterScreen when Penumbra.Config.UseCharacterCollectionInMainWindow:
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 );
case SpecialActor.ExamineScreen:
{
if( CheckWorlds( _manager.GetInspectPlayer(), out collection! ) )
{
return true;
}
if( CheckWorlds( _manager.GetCardPlayer(), out collection! ) )
{
return true;
}
if( CheckWorlds( _manager.GetGlamourPlayer(), out collection! ) )
{
return true;
}
break;
}
}
break;
}
collection = null;
return false;
}
public bool TryGetCollection( GameObject? gameObject, out ModCollection? collection )
=> TryGetCollection( _manager.FromObject( gameObject ), out collection );
public unsafe bool TryGetCollection( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* gameObject, out ModCollection? collection )
=> TryGetCollection( _manager.FromObject( gameObject ), out collection );
private bool CheckWorlds( ActorIdentifier identifier, out ModCollection? collection )
{
if( !identifier.IsValid )
{
collection = null;
return false;
}
if( _individuals.TryGetValue( identifier, out collection ) )
{
return true;
}
identifier = _manager.CreateIndividual( identifier.Type, identifier.PlayerName, ushort.MaxValue, identifier.Kind, identifier.DataId );
if( identifier.IsValid && _individuals.TryGetValue( identifier, out collection ) )
{
return true;
}
collection = null;
return false;
}
}

View file

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Interface.Internal.Notifications;
using Penumbra.GameData.Actors;
using Penumbra.String;
using Penumbra.Util;
namespace Penumbra.Collections;
public partial class IndividualCollections
{
public const int Version = 1;
internal void Migrate0To1( Dictionary< string, ModCollection > old )
{
static bool FindDataId( string name, IReadOnlyDictionary< uint, string > data, out uint dataId )
{
var kvp = data.FirstOrDefault( kvp => kvp.Value.Equals( name, StringComparison.OrdinalIgnoreCase ),
new KeyValuePair< uint, string >( uint.MaxValue, string.Empty ) );
dataId = kvp.Key;
return kvp.Value.Length > 0;
}
foreach( var (name, collection) in old )
{
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 ) )
{
kind = ObjectKind.Companion;
}
else if( FindDataId( lowerName, _manager.Mounts, out dataId ) )
{
kind = ObjectKind.MountType;
}
else if( FindDataId( lowerName, _manager.BNpcs, out dataId ) )
{
kind = ObjectKind.BattleNpc;
}
else if( FindDataId( lowerName, _manager.ENpcs, out dataId ) )
{
kind = ObjectKind.EventNpc;
}
var identifier = _manager.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 ) )
{
Penumbra.Log.Information( $"Migrated {name} ({kind.ToName()}) to NPC Identifiers [{ids}]." );
}
else
{
ChatUtil.NotificationMessage(
$"Could not migrate {name} ({collection.AnonymizedName}) which was assumed to be a {kind.ToName()} with IDs [{ids}], please look through your individual collections.",
"Migration Failure", NotificationType.Error );
}
}
// 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 );
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 ) )
{
Penumbra.Log.Information( $"Migrated {shortName} ({collection.AnonymizedName}) to Player Identifier." );
}
else
{
ChatUtil.NotificationMessage( $"Could not migrate {shortName} ({collection.AnonymizedName}), please look through your individual collections.",
"Migration Failure", NotificationType.Error );
}
}
else
{
ChatUtil.NotificationMessage(
$"Could not migrate {name} ({collection.AnonymizedName}), which can not be a player name nor is it a known NPC name, please look through your individual collections.",
"Migration Failure", NotificationType.Error );
}
}
}
}

View file

@ -1,39 +1,13 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Actors;
using Penumbra.String;
namespace Penumbra.Collections;
public partial class IndividualCollections
{
public const int Version = 1;
internal void Migrate0To1( Dictionary< string, ModCollection > old )
{
foreach( var (name, collection) in old )
{
if( ActorManager.VerifyPlayerName( name ) )
{
var identifier = _manager.CreatePlayer( ByteString.FromStringUnsafe( name, false ), ushort.MaxValue );
if( Add( name, new[] { identifier }, collection ) )
{
var shortName = string.Join( " ", name.Split().Select( n => $"{n[0]}." ) );
Penumbra.Log.Information( $"Migrated {shortName} ({collection.AnonymizedName}) to Player Identifier." );
continue;
}
}
}
}
}
public sealed partial class IndividualCollections : IReadOnlyList< (string DisplayName, ModCollection Collection) >
public sealed partial class IndividualCollections
{
private readonly ActorManager _manager;
private readonly SortedList< string, (IReadOnlyList< ActorIdentifier > Identifiers, ModCollection Collection) > _assignments = new();
@ -48,10 +22,34 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ
public IndividualCollections( ActorManager manager )
=> _manager = manager;
public bool CanAdd( params ActorIdentifier[] identifiers )
=> identifiers.Length > 0 && identifiers.All( i => i.IsValid && !Individuals.ContainsKey( i ) );
public enum AddResult
{
Valid,
AlreadySet,
Invalid,
}
public bool CanAdd( IdentifierType type, string name, ushort homeWorld, ObjectKind kind, IEnumerable< uint > dataIds, out ActorIdentifier[] identifiers )
public AddResult CanAdd( params ActorIdentifier[] identifiers )
{
if( identifiers.Length == 0 )
{
return AddResult.Invalid;
}
if( identifiers.Any( i => !i.IsValid ) )
{
return AddResult.Invalid;
}
if( identifiers.Any( Individuals.ContainsKey ) )
{
return AddResult.AlreadySet;
}
return AddResult.Valid;
}
public AddResult CanAdd( IdentifierType type, string name, ushort homeWorld, ObjectKind kind, IEnumerable< uint > dataIds, out ActorIdentifier[] identifiers )
{
identifiers = Array.Empty< ActorIdentifier >();
@ -60,7 +58,7 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ
case IdentifierType.Player:
if( !ByteString.FromString( name, out var playerName ) )
{
return false;
return AddResult.Invalid;
}
var identifier = _manager.CreatePlayer( playerName, homeWorld );
@ -69,7 +67,7 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ
case IdentifierType.Owned:
if( !ByteString.FromString( name, out var ownerName ) )
{
return false;
return AddResult.Invalid;
}
identifiers = dataIds.Select( id => _manager.CreateOwned( ownerName, homeWorld, kind, id ) ).ToArray();
@ -119,7 +117,7 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ
public bool Add( string displayName, ActorIdentifier[] identifiers, ModCollection collection )
{
if( !CanAdd( identifiers ) || _assignments.ContainsKey( displayName ) )
if( CanAdd( identifiers ) != AddResult.Valid || _assignments.ContainsKey( displayName ) )
{
return false;
}
@ -177,49 +175,4 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ
return true;
}
public IEnumerator< (string DisplayName, ModCollection Collection) > GetEnumerator()
=> _assignments.Select( kvp => ( kvp.Key, kvp.Value.Collection ) ).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public int Count
=> _assignments.Count;
public (string DisplayName, ModCollection Collection) this[ int index ]
=> ( _assignments.Keys[ index ], _assignments.Values[ index ].Collection );
public bool TryGetCollection( ActorIdentifier identifier, out ModCollection? collection )
{
collection = null;
if( !identifier.IsValid )
{
return false;
}
if( _individuals.TryGetValue( identifier, out collection ) )
{
return true;
}
if( identifier.Type is not (IdentifierType.Player or IdentifierType.Owned) )
{
return false;
}
identifier = _manager.CreateIndividual( identifier.Type, identifier.PlayerName, ushort.MaxValue, identifier.Kind, identifier.DataId );
if( identifier.IsValid && _individuals.TryGetValue( identifier, out collection ) )
{
return true;
}
return false;
}
public bool TryGetCollection( GameObject? gameObject, out ModCollection? collection )
=> TryGetCollection( _manager.FromObject( gameObject ), out collection );
public unsafe bool TryGetCollection( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* gameObject, out ModCollection? collection )
=> TryGetCollection( _manager.FromObject( gameObject ), out collection );
}

View file

@ -36,6 +36,7 @@ namespace Penumbra;
public class Penumbra : IDalamudPlugin
{
public const string Repository = "https://raw.githubusercontent.com/xivdev/Penumbra/master/repo.json";
public string Name
=> "Penumbra";
@ -93,6 +94,7 @@ public class Penumbra : IDalamudPlugin
Identifier = GameData.GameData.GetIdentifier( Dalamud.PluginInterface, Dalamud.GameData );
GamePathParser = GameData.GameData.GetGamePathParser();
StainManager = new StainManager( Dalamud.PluginInterface, Dalamud.GameData );
Actors = new ActorManager( Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData, Dalamud.GameGui, ResolveCutscene );
Framework = new FrameworkManager();
CharacterUtility = new CharacterUtility();
@ -112,7 +114,6 @@ public class Penumbra : IDalamudPlugin
ModFileSystem = ModFileSystem.Load();
ObjectReloader = new ObjectReloader();
PathResolver = new PathResolver( ResourceLoader );
Actors = new ActorManager( Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData, u => ( short )PathResolver.CutsceneActor( u ) );
Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand )
{
@ -296,6 +297,9 @@ public class Penumbra : IDalamudPlugin
WebServer = null;
}
private short ResolveCutscene( ushort index )
=> ( short )PathResolver.CutsceneActor( index );
public void Dispose()
{
StainManager?.Dispose();
@ -586,7 +590,7 @@ public class Penumbra : IDalamudPlugin
return Dalamud.PluginInterface.SourceRepository.Trim().ToLowerInvariant() switch
{
null => false,
Repository => true,
Repository => true,
"https://raw.githubusercontent.com/xivdev/Penumbra/test/repo.json" => true,
_ => false,
};

View file

@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Files;
using Penumbra.Interop.Loader;
using Penumbra.Interop.Structs;
@ -167,6 +168,24 @@ public partial class ConfigWindow
return;
}
static void DrawSpecial( string name, ActorIdentifier id )
{
if( !id.IsValid )
{
return;
}
ImGuiUtil.DrawTableColumn( name );
ImGuiUtil.DrawTableColumn( string.Empty );
ImGuiUtil.DrawTableColumn( Penumbra.Actors.ToString( id ) );
ImGuiUtil.DrawTableColumn( string.Empty );
}
DrawSpecial( "Current Player", Penumbra.Actors.GetCurrentPlayer() );
DrawSpecial( "Current Inspect", Penumbra.Actors.GetInspectPlayer() );
DrawSpecial( "Current Card", Penumbra.Actors.GetCardPlayer() );
DrawSpecial( "Current Glamour", Penumbra.Actors.GetGlamourPlayer() );
foreach( var obj in Dalamud.Objects )
{
ImGuiUtil.DrawTableColumn( $"{( ( GameObject* )obj.Address )->ObjectIndex}" );

View file

@ -1,8 +1,13 @@
using System;
using System.Collections.Generic;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Utility;
using Lumina.Excel.GeneratedSheets;
using OtterGui.Log;
namespace Penumbra.Util;
@ -32,4 +37,19 @@ public static class ChatUtil
Message = payload,
} );
}
public static void NotificationMessage( string content, string? title = null, NotificationType type = NotificationType.None )
{
var logLevel = type switch
{
NotificationType.None => Logger.LogLevel.Information,
NotificationType.Success => Logger.LogLevel.Information,
NotificationType.Warning => Logger.LogLevel.Warning,
NotificationType.Error => Logger.LogLevel.Error,
NotificationType.Info => Logger.LogLevel.Information,
_ => Logger.LogLevel.Debug,
};
Dalamud.PluginInterface.UiBuilder.AddNotification( content, title, type );
Penumbra.Log.Message( logLevel, title.IsNullOrEmpty() ? content : $"[{title}] {content}" );
}
}