More Actor stuff.

This commit is contained in:
Ottermandias 2022-11-15 21:07:14 +01:00
parent 17a8e06c1d
commit 0444c28187
4 changed files with 302 additions and 105 deletions

View file

@ -8,102 +8,15 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Dalamud.Game.ClientState.Objects.Enums;
using Penumbra.GameData.Actors;
using Penumbra.String;
namespace Penumbra.Collections;
public class IndividualCollections
{
private readonly ActorManager _manager;
private readonly List< (string DisplayName, ModCollection Collection, IReadOnlyList< ActorIdentifier > Identifiers) > _assignments = new();
private readonly Dictionary< ActorIdentifier, ModCollection > _individuals = new();
public IReadOnlyList< (string DisplayName, ModCollection Collection, IReadOnlyList< ActorIdentifier > Identifiers) > Assignments
=> _assignments;
public IReadOnlyDictionary< ActorIdentifier, ModCollection > Individuals
=> _individuals;
public IndividualCollections( ActorManager manager )
=> _manager = manager;
public bool CanAdd( ActorIdentifier identifier )
=> identifier.IsValid && !Individuals.ContainsKey( identifier );
public bool CanAdd( IdentifierType type, string name, ushort homeWorld, ObjectKind kind, IEnumerable< uint > dataIds, out ActorIdentifier[] identifiers )
{
identifiers = Array.Empty< ActorIdentifier >();
switch( type )
{
case IdentifierType.Player:
{
if( !ByteString.FromString( name, out var playerName ) )
{
return false;
}
var identifier = _manager.CreatePlayer( playerName, homeWorld );
if( !CanAdd( identifier ) )
{
return false;
}
identifiers = new[] { identifier };
return true;
}
//case IdentifierType.Owned:
//{
// if( !ByteString.FromString( name, out var ownerName ) )
// {
// return false;
// }
//
// identifiers = dataIds.Select( id => _manager.CreateOwned( ownerName, homeWorld, kind, id ) ).ToArray();
// return
// identifier = _manager.CreateIndividual( type, byteName, homeWorld, kind, dataId );
// return CanAdd( identifier );
//}
//case IdentifierType.Npc:
//{
// identifier = _manager.CreateIndividual( IdentifierType.Npc, ByteString.Empty, ushort.MaxValue, kind, dataId );
// return CanAdd( identifier );
//}
default:
identifiers = Array.Empty< ActorIdentifier >();
return false;
}
}
public bool Add( string displayName, ActorIdentifier identifier, ModCollection collection )
=> Add( displayName, identifier, collection, Array.Empty< uint >() );
public bool Add( string displayName, ActorIdentifier identifier, ModCollection collection, IEnumerable< uint > additionalIds )
{
if( Individuals.ContainsKey( identifier ) )
{
return false;
}
//var identifiers = additionalIds
// .Select( id => CanAdd( identifier.Type, identifier.PlayerName, identifier.HomeWorld, identifier.Kind, id, out var value ) ? value : ActorIdentifier.Invalid )
// .Prepend( identifier )
// .ToArray();
//if( identifiers.Any( i => !i.IsValid || i.DataId == identifier.DataId ) )
//{
// return false;
//}
return true;
}
}
public partial class ModCollection
{
public sealed partial class Manager
{
public const int Version = 1;
// Is invoked after the collections actually changed.
public event CollectionChangeDelegate CollectionChanged;
@ -406,6 +319,35 @@ public partial class ModCollection
jObject.WriteTo( j );
}
// Migrate individual collections to Identifiers for 0.6.0.
private bool MigrateIndividualCollections(JObject jObject, out IndividualCollections collections)
{
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 );
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}." );
dict.Add( player, Empty );
}
else
{
dict.Add( player, this[idx] );
}
}
collections.Migrate0To1( dict );
return true;
}
public void SaveActiveCollections()
{

View file

@ -0,0 +1,225 @@
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) >
{
private readonly ActorManager _manager;
private readonly SortedList< string, (IReadOnlyList< ActorIdentifier > Identifiers, ModCollection Collection) > _assignments = new();
private readonly Dictionary< ActorIdentifier, ModCollection > _individuals = new();
public IReadOnlyDictionary< string, (IReadOnlyList< ActorIdentifier > Identifiers, ModCollection Collection) > Assignments
=> _assignments;
public IReadOnlyDictionary< ActorIdentifier, ModCollection > Individuals
=> _individuals;
public IndividualCollections( ActorManager manager )
=> _manager = manager;
public bool CanAdd( params ActorIdentifier[] identifiers )
=> identifiers.Length > 0 && identifiers.All( i => i.IsValid && !Individuals.ContainsKey( i ) );
public bool CanAdd( IdentifierType type, string name, ushort homeWorld, ObjectKind kind, IEnumerable< uint > dataIds, out ActorIdentifier[] identifiers )
{
identifiers = Array.Empty< ActorIdentifier >();
switch( type )
{
case IdentifierType.Player:
if( !ByteString.FromString( name, out var playerName ) )
{
return false;
}
var identifier = _manager.CreatePlayer( playerName, homeWorld );
identifiers = new[] { identifier };
break;
case IdentifierType.Owned:
if( !ByteString.FromString( name, out var ownerName ) )
{
return false;
}
identifiers = dataIds.Select( id => _manager.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();
break;
default:
identifiers = Array.Empty< ActorIdentifier >();
break;
}
return CanAdd( identifiers );
}
public ActorIdentifier[] GetGroup( ActorIdentifier identifier )
{
if( !identifier.IsValid )
{
return Array.Empty< ActorIdentifier >();
}
static ActorIdentifier[] CreateNpcs( ActorManager manager, ActorIdentifier identifier )
{
var name = manager.ToName( identifier.Kind, identifier.DataId );
var table = identifier.Kind switch
{
ObjectKind.BattleNpc => manager.BNpcs,
ObjectKind.EventNpc => manager.ENpcs,
ObjectKind.Companion => manager.Companions,
ObjectKind.MountType => manager.Mounts,
_ => throw new NotImplementedException(),
};
return table.Where( kvp => kvp.Value == name )
.Select( kvp => manager.CreateIndividual( identifier.Type, identifier.PlayerName, identifier.HomeWorld, identifier.Kind, kvp.Key ) ).ToArray();
}
return identifier.Type switch
{
IdentifierType.Player => new[] { identifier.CreatePermanent() },
IdentifierType.Special => new[] { identifier },
IdentifierType.Owned => CreateNpcs( _manager, identifier.CreatePermanent() ),
IdentifierType.Npc => CreateNpcs( _manager, identifier ),
_ => Array.Empty< ActorIdentifier >(),
};
}
public bool Add( string displayName, ActorIdentifier[] identifiers, ModCollection collection )
{
if( !CanAdd( identifiers ) || _assignments.ContainsKey( displayName ) )
{
return false;
}
_assignments.Add( displayName, ( identifiers, collection ) );
foreach( var identifier in identifiers )
{
_individuals.Add( identifier, collection );
}
return true;
}
public bool ChangeCollection( string displayName, ModCollection newCollection )
{
var displayIndex = _assignments.IndexOfKey( displayName );
return ChangeCollection( displayIndex, newCollection );
}
public bool ChangeCollection( int displayIndex, ModCollection newCollection )
{
if( displayIndex < 0 || displayIndex >= _assignments.Count || _assignments.Values[ displayIndex ].Collection == newCollection )
{
return false;
}
_assignments.Values[ displayIndex ] = _assignments.Values[ displayIndex ] with { Collection = newCollection };
foreach( var identifier in _assignments.Values[ displayIndex ].Identifiers )
{
_individuals[ identifier ] = newCollection;
}
return true;
}
public bool Delete( string displayName )
{
var displayIndex = _assignments.IndexOfKey( displayName );
return Delete( displayIndex );
}
public bool Delete( int displayIndex )
{
if( displayIndex < 0 || displayIndex >= _assignments.Count )
{
return false;
}
var (identifiers, _) = _assignments.Values[ displayIndex ];
_assignments.RemoveAt( displayIndex );
foreach( var identifier in identifiers )
{
_individuals.Remove( identifier );
}
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

@ -8,6 +8,7 @@ using System.Text;
using Dalamud.Game.Command;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using Dalamud.Utility;
using EmbedIO;
using EmbedIO.WebApi;
using ImGuiNET;
@ -34,6 +35,7 @@ namespace Penumbra;
public class Penumbra : IDalamudPlugin
{
public const string Repository = "https://raw.githubusercontent.com/xivdev/Penumbra/master/repo.json";
public string Name
=> "Penumbra";
@ -46,6 +48,7 @@ public class Penumbra : IDalamudPlugin
public static bool DevPenumbraExists;
public static bool IsNotInstalledPenumbra;
public static bool IsValidSourceRepo;
public static Logger Log { get; private set; } = null!;
public static Configuration Config { get; private set; } = null!;
@ -84,11 +87,12 @@ public class Penumbra : IDalamudPlugin
{
Dalamud.Initialize( pluginInterface );
Log = new Logger();
DevPenumbraExists = CheckDevPluginPenumbra();
IsNotInstalledPenumbra = CheckIsNotInstalled();
IsValidSourceRepo = CheckSourceRepo();
Identifier = GameData.GameData.GetIdentifier( Dalamud.PluginInterface, Dalamud.GameData );
GamePathParser = GameData.GameData.GetGamePathParser();
StainManager = new StainManager( Dalamud.PluginInterface, Dalamud.GameData );
DevPenumbraExists = CheckDevPluginPenumbra();
IsNotInstalledPenumbra = CheckIsNotInstalled();
Framework = new FrameworkManager();
CharacterUtility = new CharacterUtility();
@ -153,9 +157,9 @@ public class Penumbra : IDalamudPlugin
}
else
{
Log.Information( $"Penumbra Version {Version}, Commit #{CommitHash} successfully Loaded." );
Log.Information( $"Penumbra Version {Version}, Commit #{CommitHash} successfully Loaded from {pluginInterface.SourceRepository}." );
}
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
OtterTex.NativeDll.Initialize( Dalamud.PluginInterface.AssemblyLocation.DirectoryName );
@ -561,7 +565,7 @@ public class Penumbra : IDalamudPlugin
#endif
}
// Check if the loaded version of penumbra itself is in devPlugins.
// Check if the loaded version of Penumbra itself is in devPlugins.
private static bool CheckIsNotInstalled()
{
#if !DEBUG
@ -572,6 +576,22 @@ public class Penumbra : IDalamudPlugin
return !ret;
#else
return false;
#endif
}
// Check if the loaded version of Penumbra is installed from a valid source repo.
private static bool CheckSourceRepo()
{
#if !DEBUG
return Dalamud.PluginInterface.SourceRepository.Trim().ToLowerInvariant() switch
{
null => false,
Repository => true,
"https://raw.githubusercontent.com/xivdev/Penumbra/test/repo.json" => true,
_ => false,
};
#else
return true;
#endif
}
}

View file

@ -59,7 +59,14 @@ public sealed partial class ConfigWindow : Window, IDisposable
DrawProblemWindow( $"There were {Penumbra.ImcExceptions.Count} errors while trying to load IMC files from the game data.\n"
+ "This usually means that your game installation was corrupted by updating the game while having TexTools mods still active.\n"
+ "It is recommended to not use TexTools and Penumbra (or other Lumina-based tools) at the same time.\n\n"
+ "Please use the Launcher's Repair Game Files function to repair your client installation." );
+ "Please use the Launcher's Repair Game Files function to repair your client installation.", true );
}
else if( !Penumbra.IsValidSourceRepo )
{
DrawProblemWindow(
$"You are loading a release version of Penumbra from the repository \"{Dalamud.PluginInterface.SourceRepository}\" instead of the official repository.\n"
+ $"Please use the official repository at {Penumbra.Repository}.\n\n"
+ "If you are developing for Penumbra and see this, you should compile your version in debug mode to avoid it.", false );
}
else if( Penumbra.IsNotInstalledPenumbra )
{
@ -67,7 +74,7 @@ public sealed partial class ConfigWindow : Window, IDisposable
$"You are loading a release version of Penumbra from \"{Dalamud.PluginInterface.AssemblyLocation.Directory?.FullName ?? "Unknown"}\" instead of the installedPlugins directory.\n\n"
+ "You should not install Penumbra manually, but rather add the plugin repository under settings and then install it via the plugin installer.\n\n"
+ "If you do not know how to do this, please take a look at the readme in Penumbras github repository or join us in discord.\n"
+ "If you are developing for Penumbra and see this, you should compile your version in debug mode to avoid it." );
+ "If you are developing for Penumbra and see this, you should compile your version in debug mode to avoid it.", false );
}
else if( Penumbra.DevPenumbraExists )
{
@ -75,7 +82,7 @@ public sealed partial class ConfigWindow : Window, IDisposable
$"You are loading a installed version of Penumbra from \"{Dalamud.PluginInterface.AssemblyLocation.Directory?.FullName ?? "Unknown"}\", "
+ "but also still have some remnants of a custom install of Penumbra in your devPlugins folder.\n\n"
+ "This can cause some issues, so please go to your \"%%appdata%%\\XIVLauncher\\devPlugins\" folder and delete the Penumbra folder from there.\n\n"
+ "If you are developing for Penumbra, try to avoid mixing versions. This warning will not appear if compiled in Debug mode." );
+ "If you are developing for Penumbra, try to avoid mixing versions. This warning will not appear if compiled in Debug mode.", false );
}
else
{
@ -96,12 +103,12 @@ public sealed partial class ConfigWindow : Window, IDisposable
}
}
private static void DrawProblemWindow( string text )
private static void DrawProblemWindow( string text, bool withExceptions )
{
using var color = ImRaii.PushColor( ImGuiCol.Text, Colors.RegexWarningBorder );
ImGui.NewLine();
ImGui.NewLine();
ImGui.TextWrapped( text );
ImGuiUtil.TextWrapped( text );
color.Pop();
ImGui.NewLine();
@ -112,14 +119,17 @@ public sealed partial class ConfigWindow : Window, IDisposable
ImGui.NewLine();
ImGui.NewLine();
ImGui.TextUnformatted( "Exceptions" );
ImGui.Separator();
using var box = ImRaii.ListBox( "##Exceptions", new Vector2(-1, -1) );
foreach( var exception in Penumbra.ImcExceptions )
if( withExceptions )
{
ImGuiUtil.TextWrapped( exception.ToString() );
ImGui.TextUnformatted( "Exceptions" );
ImGui.Separator();
ImGui.NewLine();
using var box = ImRaii.ListBox( "##Exceptions", new Vector2( -1, -1 ) );
foreach( var exception in Penumbra.ImcExceptions )
{
ImGuiUtil.TextWrapped( exception.ToString() );
ImGui.Separator();
ImGui.NewLine();
}
}
}