mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Continued work on actor identification, migration seems to work.
This commit is contained in:
parent
0444c28187
commit
bda3c1f1ac
11 changed files with 407 additions and 115 deletions
|
|
@ -59,7 +59,7 @@ public readonly struct ActorIdentifier : IEquatable<ActorIdentifier>
|
|||
{
|
||||
IdentifierType.Player => $"{PlayerName} ({HomeWorld})",
|
||||
IdentifierType.Owned => $"{PlayerName}s {Kind} {DataId} ({HomeWorld})",
|
||||
IdentifierType.Special => ActorManager.ToName(Special),
|
||||
IdentifierType.Special => Special.ToName(),
|
||||
IdentifierType.Npc =>
|
||||
Index == ushort.MaxValue
|
||||
? $"{Kind} #{DataId}"
|
||||
|
|
@ -147,4 +147,39 @@ public static class ActorManagerExtensions
|
|||
_ => 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",
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,17 @@ using Dalamud;
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Plugin;
|
||||
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 Penumbra.GameData.Data;
|
||||
using Penumbra.String;
|
||||
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
|
||||
|
||||
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>
|
||||
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)
|
||||
: 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)
|
||||
: base(pluginInterface, language, 1)
|
||||
{
|
||||
_objects = objects;
|
||||
_gameGui = gameGui;
|
||||
_clientState = state;
|
||||
_toParentIdx = toParentIdx;
|
||||
|
||||
|
|
@ -50,6 +58,39 @@ public sealed partial class ActorManager : DataSharer
|
|||
ENpcs = TryCatchData("ENpcs", () => CreateENpcData(gameData));
|
||||
|
||||
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()
|
||||
|
|
@ -66,6 +107,7 @@ public sealed partial class ActorManager : DataSharer
|
|||
|
||||
private readonly ObjectTable _objects;
|
||||
private readonly ClientState _clientState;
|
||||
private readonly GameGui _gameGui;
|
||||
|
||||
private readonly Func<ushort, short> _toParentIdx;
|
||||
|
||||
|
|
@ -93,4 +135,20 @@ public sealed partial class ActorManager : DataSharer
|
|||
=> gameData.GetExcelSheet<ENpcResident>(Language)!
|
||||
.Where(e => e.Singular.RawData.Length > 0)
|
||||
.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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
/// Use stored data to convert an ActorIdentifier to a string.
|
||||
/// </summary>
|
||||
|
|
@ -59,12 +65,12 @@ public partial class ActorManager
|
|||
return id.Type switch
|
||||
{
|
||||
IdentifierType.Player => id.HomeWorld != _clientState.LocalPlayer?.HomeWorld.Id
|
||||
? $"{id.PlayerName} ({Worlds[id.HomeWorld]})"
|
||||
? $"{id.PlayerName} ({ToWorldName(id.HomeWorld)})"
|
||||
: id.PlayerName.ToString(),
|
||||
IdentifierType.Owned => id.HomeWorld != _clientState.LocalPlayer?.HomeWorld.Id
|
||||
? $"{id.PlayerName} ({Worlds[id.HomeWorld]})'s {ToName(id.Kind, id.DataId)}"
|
||||
: $"{id.PlayerName}s {ToName(id.Kind, id.DataId)}",
|
||||
IdentifierType.Special => ToName(id.Special),
|
||||
? $"{id.PlayerName} ({ToWorldName(id.HomeWorld)})'s {ToName(id.Kind, id.DataId)}"
|
||||
: $"{id.PlayerName}s {ToName(id.Kind, id.DataId)}",
|
||||
IdentifierType.Special => id.Special.ToName(),
|
||||
IdentifierType.Npc =>
|
||||
id.Index == ushort.MaxValue
|
||||
? 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>
|
||||
/// Convert a given ID for a certain ObjectKind to a name.
|
||||
/// </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>
|
||||
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>
|
||||
public static bool VerifySpecial(SpecialActor actor)
|
||||
|
|
@ -339,6 +331,7 @@ public partial class ActorManager
|
|||
{
|
||||
return index switch
|
||||
{
|
||||
ushort.MaxValue => true,
|
||||
< 200 => index % 2 == 0,
|
||||
> (ushort)SpecialActor.Portrait => index < 426,
|
||||
_ => false,
|
||||
|
|
@ -360,6 +353,8 @@ public partial class ActorManager
|
|||
public bool VerifyNpcData(ObjectKind kind, uint dataId)
|
||||
=> kind switch
|
||||
{
|
||||
ObjectKind.MountType => Mounts.ContainsKey(dataId),
|
||||
ObjectKind.Companion => Companions.ContainsKey(dataId),
|
||||
ObjectKind.BattleNpc => BNpcs.ContainsKey(dataId),
|
||||
ObjectKind.EventNpc => ENpcs.ContainsKey(dataId),
|
||||
_ => false,
|
||||
|
|
|
|||
|
|
@ -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,10 +323,9 @@ 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;
|
||||
|
||||
|
|
@ -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 ),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
117
Penumbra/Collections/IndividualCollections.Access.cs
Normal file
117
Penumbra/Collections/IndividualCollections.Access.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
89
Penumbra/Collections/IndividualCollections.Files.cs
Normal file
89
Penumbra/Collections/IndividualCollections.Files.cs
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 );
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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}" );
|
||||
|
|
|
|||
|
|
@ -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}" );
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue