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

@ -59,7 +59,7 @@ public readonly struct ActorIdentifier : IEquatable<ActorIdentifier>
{ {
IdentifierType.Player => $"{PlayerName} ({HomeWorld})", IdentifierType.Player => $"{PlayerName} ({HomeWorld})",
IdentifierType.Owned => $"{PlayerName}s {Kind} {DataId} ({HomeWorld})", IdentifierType.Owned => $"{PlayerName}s {Kind} {DataId} ({HomeWorld})",
IdentifierType.Special => ActorManager.ToName(Special), IdentifierType.Special => Special.ToName(),
IdentifierType.Npc => IdentifierType.Npc =>
Index == ushort.MaxValue Index == ushort.MaxValue
? $"{Kind} #{DataId}" ? $"{Kind} #{DataId}"
@ -147,4 +147,39 @@ public static class ActorManagerExtensions
_ => false, _ => false,
}; };
} }
public static string ToName(this ObjectKind kind)
=> kind switch
{
ObjectKind.None => "Unknown",
ObjectKind.BattleNpc => "Battle NPC",
ObjectKind.EventNpc => "Event NPC",
ObjectKind.MountType => "Mount",
ObjectKind.Companion => "Companion",
_ => kind.ToString(),
};
public static string ToName(this IdentifierType type)
=> type switch
{
IdentifierType.Player => "Player",
IdentifierType.Owned => "Owned NPC",
IdentifierType.Special => "Special Actor",
IdentifierType.Npc => "NPC",
_ => "Invalid",
};
/// <summary>
/// Fixed names for special actors.
/// </summary>
public static string ToName(this SpecialActor actor)
=> actor switch
{
SpecialActor.CharacterScreen => "Character Screen Actor",
SpecialActor.ExamineScreen => "Examine Screen Actor",
SpecialActor.FittingRoom => "Fitting Room Actor",
SpecialActor.DyePreview => "Dye Preview Actor",
SpecialActor.Portrait => "Portrait Actor",
_ => "Invalid",
};
} }

View file

@ -6,10 +6,17 @@ using Dalamud;
using Dalamud.Data; using Dalamud.Data;
using Dalamud.Game.ClientState; using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Gui;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Utility; using Dalamud.Utility;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.String;
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
namespace Penumbra.GameData.Actors; namespace Penumbra.GameData.Actors;
@ -30,16 +37,17 @@ public sealed partial class ActorManager : DataSharer
/// <summary> Valid ENPC names in title case by ENPC id. </summary> /// <summary> Valid ENPC names in title case by ENPC id. </summary>
public IReadOnlyDictionary<uint, string> ENpcs { get; } public IReadOnlyDictionary<uint, string> ENpcs { get; }
public ActorManager(DalamudPluginInterface pluginInterface, ObjectTable objects, ClientState state, DataManager gameData, public ActorManager(DalamudPluginInterface pluginInterface, ObjectTable objects, ClientState state, DataManager gameData, GameGui gameGui,
Func<ushort, short> toParentIdx) Func<ushort, short> toParentIdx)
: this(pluginInterface, objects, state, gameData, gameData.Language, toParentIdx) : this(pluginInterface, objects, state, gameData, gameGui, gameData.Language, toParentIdx)
{ } { }
public ActorManager(DalamudPluginInterface pluginInterface, ObjectTable objects, ClientState state, DataManager gameData, public ActorManager(DalamudPluginInterface pluginInterface, ObjectTable objects, ClientState state, DataManager gameData, GameGui gameGui,
ClientLanguage language, Func<ushort, short> toParentIdx) ClientLanguage language, Func<ushort, short> toParentIdx)
: base(pluginInterface, language, 1) : base(pluginInterface, language, 1)
{ {
_objects = objects; _objects = objects;
_gameGui = gameGui;
_clientState = state; _clientState = state;
_toParentIdx = toParentIdx; _toParentIdx = toParentIdx;
@ -50,6 +58,39 @@ public sealed partial class ActorManager : DataSharer
ENpcs = TryCatchData("ENpcs", () => CreateENpcData(gameData)); ENpcs = TryCatchData("ENpcs", () => CreateENpcData(gameData));
ActorIdentifier.Manager = this; ActorIdentifier.Manager = this;
SignatureHelper.Initialise(this);
}
public unsafe ActorIdentifier GetCurrentPlayer()
{
var address = (Character*)(_objects[0]?.Address ?? IntPtr.Zero);
return address == null ? ActorIdentifier.Invalid : CreatePlayer(new ByteString(address->GameObject.Name), address->HomeWorld);
}
public ActorIdentifier GetInspectPlayer()
{
var addon = _gameGui.GetAddonByName("CharacterInspect", 1);
if (addon == IntPtr.Zero)
return ActorIdentifier.Invalid;
return CreatePlayer(InspectName, InspectWorldId);
}
public unsafe ActorIdentifier GetCardPlayer()
{
var agent = AgentCharaCard.Instance();
if (agent == null || agent->Data == null)
return ActorIdentifier.Invalid;
var worldId = *(ushort*)((byte*)agent->Data + 0xC0);
return CreatePlayer(new ByteString(agent->Data->Name.StringPtr), worldId);
}
public ActorIdentifier GetGlamourPlayer()
{
var addon = _gameGui.GetAddonByName("MiragePrismMiragePlate", 1);
return addon == IntPtr.Zero ? ActorIdentifier.Invalid : GetCurrentPlayer();
} }
protected override void DisposeInternal() protected override void DisposeInternal()
@ -66,6 +107,7 @@ public sealed partial class ActorManager : DataSharer
private readonly ObjectTable _objects; private readonly ObjectTable _objects;
private readonly ClientState _clientState; private readonly ClientState _clientState;
private readonly GameGui _gameGui;
private readonly Func<ushort, short> _toParentIdx; private readonly Func<ushort, short> _toParentIdx;
@ -93,4 +135,20 @@ public sealed partial class ActorManager : DataSharer
=> gameData.GetExcelSheet<ENpcResident>(Language)! => gameData.GetExcelSheet<ENpcResident>(Language)!
.Where(e => e.Singular.RawData.Length > 0) .Where(e => e.Singular.RawData.Length > 0)
.ToDictionary(e => e.RowId, e => CultureInfo.InvariantCulture.TextInfo.ToTitleCase(e.Singular.ToDalamudString().ToString())); .ToDictionary(e => e.RowId, e => CultureInfo.InvariantCulture.TextInfo.ToTitleCase(e.Singular.ToDalamudString().ToString()));
[Signature("0F B7 0D ?? ?? ?? ?? C7 85", ScanType = ScanType.StaticAddress)]
private static unsafe ushort* _inspectTitleId = null!;
[Signature("0F B7 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 8B D0", ScanType = ScanType.StaticAddress)]
private static unsafe ushort* _inspectWorldId = null!;
private static unsafe ushort InspectTitleId
=> *_inspectTitleId;
private static unsafe ByteString InspectName
=> new((byte*)(_inspectWorldId + 1));
private static unsafe ushort InspectWorldId
=> *_inspectWorldId;
} }

View file

@ -51,6 +51,12 @@ public partial class ActorManager
} }
} }
/// <summary>
/// Return the world name including the All Worlds option.
/// </summary>
public string ToWorldName(ushort worldId)
=> worldId == ushort.MaxValue ? "Any World" : Worlds.TryGetValue(worldId, out var name) ? name : "Invalid";
/// <summary> /// <summary>
/// Use stored data to convert an ActorIdentifier to a string. /// Use stored data to convert an ActorIdentifier to a string.
/// </summary> /// </summary>
@ -59,12 +65,12 @@ public partial class ActorManager
return id.Type switch return id.Type switch
{ {
IdentifierType.Player => id.HomeWorld != _clientState.LocalPlayer?.HomeWorld.Id IdentifierType.Player => id.HomeWorld != _clientState.LocalPlayer?.HomeWorld.Id
? $"{id.PlayerName} ({Worlds[id.HomeWorld]})" ? $"{id.PlayerName} ({ToWorldName(id.HomeWorld)})"
: id.PlayerName.ToString(), : id.PlayerName.ToString(),
IdentifierType.Owned => id.HomeWorld != _clientState.LocalPlayer?.HomeWorld.Id IdentifierType.Owned => id.HomeWorld != _clientState.LocalPlayer?.HomeWorld.Id
? $"{id.PlayerName} ({Worlds[id.HomeWorld]})'s {ToName(id.Kind, id.DataId)}" ? $"{id.PlayerName} ({ToWorldName(id.HomeWorld)})'s {ToName(id.Kind, id.DataId)}"
: $"{id.PlayerName}s {ToName(id.Kind, id.DataId)}", : $"{id.PlayerName}s {ToName(id.Kind, id.DataId)}",
IdentifierType.Special => ToName(id.Special), IdentifierType.Special => id.Special.ToName(),
IdentifierType.Npc => IdentifierType.Npc =>
id.Index == ushort.MaxValue id.Index == ushort.MaxValue
? ToName(id.Kind, id.DataId) ? ToName(id.Kind, id.DataId)
@ -74,20 +80,6 @@ public partial class ActorManager
} }
/// <summary>
/// Fixed names for special actors.
/// </summary>
public static string ToName(SpecialActor actor)
=> actor switch
{
SpecialActor.CharacterScreen => "Character Screen Actor",
SpecialActor.ExamineScreen => "Examine Screen Actor",
SpecialActor.FittingRoom => "Fitting Room Actor",
SpecialActor.DyePreview => "Dye Preview Actor",
SpecialActor.Portrait => "Portrait Actor",
_ => "Invalid",
};
/// <summary> /// <summary>
/// Convert a given ID for a certain ObjectKind to a name. /// Convert a given ID for a certain ObjectKind to a name.
/// </summary> /// </summary>
@ -328,7 +320,7 @@ public partial class ActorManager
/// <summary> Checks if the world is a valid public world or ushort.MaxValue (any world). </summary> /// <summary> Checks if the world is a valid public world or ushort.MaxValue (any world). </summary>
public bool VerifyWorld(ushort worldId) public bool VerifyWorld(ushort worldId)
=> Worlds.ContainsKey(worldId); => worldId == ushort.MaxValue || Worlds.ContainsKey(worldId);
/// <summary> Verify that the enum value is a specific actor and return the name if it is. </summary> /// <summary> Verify that the enum value is a specific actor and return the name if it is. </summary>
public static bool VerifySpecial(SpecialActor actor) public static bool VerifySpecial(SpecialActor actor)
@ -339,6 +331,7 @@ public partial class ActorManager
{ {
return index switch return index switch
{ {
ushort.MaxValue => true,
< 200 => index % 2 == 0, < 200 => index % 2 == 0,
> (ushort)SpecialActor.Portrait => index < 426, > (ushort)SpecialActor.Portrait => index < 426,
_ => false, _ => false,
@ -360,6 +353,8 @@ public partial class ActorManager
public bool VerifyNpcData(ObjectKind kind, uint dataId) public bool VerifyNpcData(ObjectKind kind, uint dataId)
=> kind switch => kind switch
{ {
ObjectKind.MountType => Mounts.ContainsKey(dataId),
ObjectKind.Companion => Companions.ContainsKey(dataId),
ObjectKind.BattleNpc => BNpcs.ContainsKey(dataId), ObjectKind.BattleNpc => BNpcs.ContainsKey(dataId),
ObjectKind.EventNpc => ENpcs.ContainsKey(dataId), ObjectKind.EventNpc => ENpcs.ContainsKey(dataId),
_ => false, _ => false,

View file

@ -37,6 +37,7 @@ public partial class ModCollection
// The list of character collections. // The list of character collections.
private readonly Dictionary< string, ModCollection > _characters = new(); private readonly Dictionary< string, ModCollection > _characters = new();
public readonly IndividualCollections Individuals = new(Penumbra.Actors);
public IReadOnlyDictionary< string, ModCollection > Characters public IReadOnlyDictionary< string, ModCollection > Characters
=> _characters; => _characters;
@ -288,6 +289,8 @@ public partial class ModCollection
{ {
SaveActiveCollections(); SaveActiveCollections();
} }
MigrateIndividualCollections( jObject );
} }
// Migrate ungendered collections to Male and Female for 0.5.9.0. // 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. // 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; var version = jObject[ nameof( Version ) ]?.Value< int >() ?? 0;
collections = new IndividualCollections( Penumbra.Actors );
if( version > 0 ) if( version > 0 )
return false; return false;
// Load character collections. If a player name comes up multiple times, the last one is applied. // 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 characters = jObject[nameof( Characters )]?.ToObject<Dictionary<string, string>>() ?? new Dictionary<string, string>();
var dict = new Dictionary< string, ModCollection >( characters.Count ); var dict = new Dictionary< string, ModCollection >( characters.Count );
@ -340,15 +342,14 @@ public partial class ModCollection
} }
else else
{ {
dict.Add( player, this[idx] ); dict.Add( player, this[ idx ] );
} }
} }
collections.Migrate0To1( dict ); Individuals.Migrate0To1( dict );
return true; return true;
} }
public void SaveActiveCollections() public void SaveActiveCollections()
{ {
Penumbra.Framework.RegisterDelayed( nameof( SaveActiveCollections ), Penumbra.Framework.RegisterDelayed( nameof( SaveActiveCollections ),

View file

@ -94,11 +94,12 @@ public enum CollectionType : byte
MaleVeenaNpc, MaleVeenaNpc,
FemaleVeenaNpc, FemaleVeenaNpc,
Inactive, // A collection was added or removed Inactive, // A collection was added or removed
Default, // The default collection was changed Default, // The default collection was changed
Interface, // The ui collection was changed Interface, // The ui collection was changed
Character, // A character collection was changed Character, // A character collection was changed
Current, // The current collection was changed Individual, // An Individual collection was changed
Current, // The current collection was changed
} }
public static class CollectionTypeExtensions 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;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.String; using Penumbra.String;
namespace Penumbra.Collections; namespace Penumbra.Collections;
public partial class IndividualCollections public sealed 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 ActorManager _manager;
private readonly SortedList< string, (IReadOnlyList< ActorIdentifier > Identifiers, ModCollection Collection) > _assignments = new(); 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 ) public IndividualCollections( ActorManager manager )
=> _manager = manager; => _manager = manager;
public bool CanAdd( params ActorIdentifier[] identifiers ) public enum AddResult
=> identifiers.Length > 0 && identifiers.All( i => i.IsValid && !Individuals.ContainsKey( i ) ); {
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 >(); identifiers = Array.Empty< ActorIdentifier >();
@ -60,7 +58,7 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ
case IdentifierType.Player: case IdentifierType.Player:
if( !ByteString.FromString( name, out var playerName ) ) if( !ByteString.FromString( name, out var playerName ) )
{ {
return false; return AddResult.Invalid;
} }
var identifier = _manager.CreatePlayer( playerName, homeWorld ); var identifier = _manager.CreatePlayer( playerName, homeWorld );
@ -69,7 +67,7 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ
case IdentifierType.Owned: case IdentifierType.Owned:
if( !ByteString.FromString( name, out var ownerName ) ) if( !ByteString.FromString( name, out var ownerName ) )
{ {
return false; return AddResult.Invalid;
} }
identifiers = dataIds.Select( id => _manager.CreateOwned( ownerName, homeWorld, kind, id ) ).ToArray(); 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 ) 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; return false;
} }
@ -177,49 +175,4 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ
return true; 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 class Penumbra : IDalamudPlugin
{ {
public const string Repository = "https://raw.githubusercontent.com/xivdev/Penumbra/master/repo.json"; public const string Repository = "https://raw.githubusercontent.com/xivdev/Penumbra/master/repo.json";
public string Name public string Name
=> "Penumbra"; => "Penumbra";
@ -93,6 +94,7 @@ public class Penumbra : IDalamudPlugin
Identifier = GameData.GameData.GetIdentifier( Dalamud.PluginInterface, Dalamud.GameData ); Identifier = GameData.GameData.GetIdentifier( Dalamud.PluginInterface, Dalamud.GameData );
GamePathParser = GameData.GameData.GetGamePathParser(); GamePathParser = GameData.GameData.GetGamePathParser();
StainManager = new StainManager( Dalamud.PluginInterface, Dalamud.GameData ); StainManager = new StainManager( Dalamud.PluginInterface, Dalamud.GameData );
Actors = new ActorManager( Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData, Dalamud.GameGui, ResolveCutscene );
Framework = new FrameworkManager(); Framework = new FrameworkManager();
CharacterUtility = new CharacterUtility(); CharacterUtility = new CharacterUtility();
@ -112,7 +114,6 @@ public class Penumbra : IDalamudPlugin
ModFileSystem = ModFileSystem.Load(); ModFileSystem = ModFileSystem.Load();
ObjectReloader = new ObjectReloader(); ObjectReloader = new ObjectReloader();
PathResolver = new PathResolver( ResourceLoader ); 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 ) Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand )
{ {
@ -296,6 +297,9 @@ public class Penumbra : IDalamudPlugin
WebServer = null; WebServer = null;
} }
private short ResolveCutscene( ushort index )
=> ( short )PathResolver.CutsceneActor( index );
public void Dispose() public void Dispose()
{ {
StainManager?.Dispose(); StainManager?.Dispose();
@ -586,7 +590,7 @@ public class Penumbra : IDalamudPlugin
return Dalamud.PluginInterface.SourceRepository.Trim().ToLowerInvariant() switch return Dalamud.PluginInterface.SourceRepository.Trim().ToLowerInvariant() switch
{ {
null => false, null => false,
Repository => true, Repository => true,
"https://raw.githubusercontent.com/xivdev/Penumbra/test/repo.json" => true, "https://raw.githubusercontent.com/xivdev/Penumbra/test/repo.json" => true,
_ => false, _ => false,
}; };

View file

@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
using Penumbra.Interop.Loader; using Penumbra.Interop.Loader;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
@ -167,6 +168,24 @@ public partial class ConfigWindow
return; 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 ) foreach( var obj in Dalamud.Objects )
{ {
ImGuiUtil.DrawTableColumn( $"{( ( GameObject* )obj.Address )->ObjectIndex}" ); ImGuiUtil.DrawTableColumn( $"{( ( GameObject* )obj.Address )->ObjectIndex}" );

View file

@ -1,8 +1,13 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Dalamud.Game.Text; using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Utility;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using OtterGui.Log;
namespace Penumbra.Util; namespace Penumbra.Util;
@ -32,4 +37,19 @@ public static class ChatUtil
Message = payload, 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}" );
}
} }