From 0444c2818740ec2f781fe4f49b831658be3f3555 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 15 Nov 2022 21:07:14 +0100 Subject: [PATCH] More Actor stuff. --- .../Collections/CollectionManager.Active.cs | 120 +++------- Penumbra/Collections/IndividualCollections.cs | 225 ++++++++++++++++++ Penumbra/Penumbra.cs | 30 ++- Penumbra/UI/ConfigWindow.cs | 32 ++- 4 files changed, 302 insertions(+), 105 deletions(-) create mode 100644 Penumbra/Collections/IndividualCollections.cs diff --git a/Penumbra/Collections/CollectionManager.Active.cs b/Penumbra/Collections/CollectionManager.Active.cs index 99ba3398..1afcdf3f 100644 --- a/Penumbra/Collections/CollectionManager.Active.cs +++ b/Penumbra/Collections/CollectionManager.Active.cs @@ -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>() ?? new Dictionary(); + 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() { diff --git a/Penumbra/Collections/IndividualCollections.cs b/Penumbra/Collections/IndividualCollections.cs new file mode 100644 index 00000000..c6516f2c --- /dev/null +++ b/Penumbra/Collections/IndividualCollections.cs @@ -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 ); +} \ No newline at end of file diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index d266339b..a2f167ce 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -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 } } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.cs b/Penumbra/UI/ConfigWindow.cs index e9ea7eff..afe8c9be 100644 --- a/Penumbra/UI/ConfigWindow.cs +++ b/Penumbra/UI/ConfigWindow.cs @@ -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(); + } } }