mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Use DataShare in ObjectIdentifier
This commit is contained in:
parent
52b2b66cd7
commit
ef3ffb5f10
17 changed files with 4138 additions and 573 deletions
3623
Penumbra.GameData/.editorconfig
Normal file
3623
Penumbra.GameData/.editorconfig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using Dalamud.Data;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.Api.Enums;
|
||||
using Action = Lumina.Excel.GeneratedSheets.Action;
|
||||
|
|
@ -18,13 +19,13 @@ public static class ChangedItemExtensions
|
|||
};
|
||||
}
|
||||
|
||||
public static object? GetObject( this ChangedItemType type, uint id )
|
||||
public static object? GetObject( this ChangedItemType type, DataManager manager, uint id )
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ChangedItemType.None => null,
|
||||
ChangedItemType.Item => ObjectIdentification.DataManager?.GetExcelSheet< Item >()?.GetRow( id ),
|
||||
ChangedItemType.Action => ObjectIdentification.DataManager?.GetExcelSheet< Action >()?.GetRow( id ),
|
||||
ChangedItemType.Item => manager.GetExcelSheet< Item >()?.GetRow( id ),
|
||||
ChangedItemType.Action => manager.GetExcelSheet< Action >()?.GetRow( id ),
|
||||
ChangedItemType.Customization => null,
|
||||
_ => throw new ArgumentOutOfRangeException( nameof( type ), type, null ),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,48 +1,73 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.GameData.Util;
|
||||
|
||||
namespace Penumbra.GameData;
|
||||
|
||||
public static class GameData
|
||||
{
|
||||
internal static ObjectIdentification? Identification;
|
||||
internal static readonly GamePathParser GamePathParser = new();
|
||||
/// <summary>
|
||||
/// Obtain an object identifier that can link a game path to game objects that use it, using your client language.
|
||||
/// </summary>
|
||||
public static IObjectIdentifier GetIdentifier(DalamudPluginInterface pluginInterface, DataManager dataManager)
|
||||
=> new ObjectIdentification(pluginInterface, dataManager, dataManager.Language);
|
||||
|
||||
public static IObjectIdentifier GetIdentifier( DataManager dataManager )
|
||||
{
|
||||
Identification ??= new ObjectIdentification( dataManager, dataManager.Language );
|
||||
return Identification;
|
||||
}
|
||||
|
||||
public static IObjectIdentifier GetIdentifier()
|
||||
{
|
||||
if( Identification == null )
|
||||
{
|
||||
throw new Exception( "Object Identification was not initialized." );
|
||||
}
|
||||
|
||||
return Identification;
|
||||
}
|
||||
/// <summary>
|
||||
/// Obtain an object identifier that can link a game path to game objects that use it using the given language.
|
||||
/// </summary>
|
||||
public static IObjectIdentifier GetIdentifier(DalamudPluginInterface pluginInterface, DataManager dataManager, ClientLanguage language)
|
||||
=> new ObjectIdentification(pluginInterface, dataManager, language);
|
||||
|
||||
/// <summary>
|
||||
/// Obtain a parser for game paths.
|
||||
/// </summary>
|
||||
public static IGamePathParser GetGamePathParser()
|
||||
=> GamePathParser;
|
||||
=> new GamePathParser();
|
||||
}
|
||||
|
||||
public interface IObjectIdentifier
|
||||
{
|
||||
public void Identify( IDictionary< string, object? > set, GamePath path );
|
||||
public Dictionary< string, object? > Identify( GamePath path );
|
||||
public Item? Identify( SetId setId, WeaponType weaponType, ushort variant, EquipSlot slot );
|
||||
public interface IObjectIdentifier : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// An accessible parser for game paths.
|
||||
/// </summary>
|
||||
public IGamePathParser GamePathParser { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Add all known game objects using the given game path to the dictionary.
|
||||
/// </summary>
|
||||
/// <param name="set">A pre-existing dictionary to which names (and optional linked objects) can be added.</param>
|
||||
/// <param name="path">The game path to identify.</param>
|
||||
public void Identify(IDictionary<string, object?> set, string path);
|
||||
|
||||
/// <summary>
|
||||
/// Return named information and possibly linked objects for all known game objects using the given path.
|
||||
/// </summary>
|
||||
/// <param name="path">The game path to identify.</param>
|
||||
public Dictionary<string, object?> Identify(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Identify an equippable item by its model values.
|
||||
/// </summary>
|
||||
/// <param name="setId">The primary model ID for the piece of equipment.</param>
|
||||
/// <param name="weaponType">The secondary model ID for weapons, WeaponType.Zero for equipment and accessories.</param>
|
||||
/// <param name="variant">The variant ID of the model.</param>
|
||||
/// <param name="slot">The equipment slot the piece of equipment uses.</param>
|
||||
/// <returns></returns>
|
||||
public IReadOnlyList<Item>? Identify(SetId setId, WeaponType weaponType, ushort variant, EquipSlot slot);
|
||||
|
||||
/// <inheritdoc cref="Identify(SetId, WeaponType, ushort, EquipSlot)"/>
|
||||
public IReadOnlyList<Item>? Identify(SetId setId, ushort variant, EquipSlot slot)
|
||||
=> Identify(setId, 0, variant, slot);
|
||||
}
|
||||
|
||||
public interface IGamePathParser
|
||||
{
|
||||
public ObjectType PathToObjectType( GamePath path );
|
||||
public GameObjectInfo GetFileInfo( GamePath path );
|
||||
public string VfxToKey( GamePath path );
|
||||
}
|
||||
public ObjectType PathToObjectType(string path);
|
||||
public GameObjectInfo GetFileInfo(string path);
|
||||
public string VfxToKey(string path);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,66 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.GameData.Util;
|
||||
|
||||
namespace Penumbra.GameData;
|
||||
|
||||
internal class GamePathParser : IGamePathParser
|
||||
{
|
||||
public GameObjectInfo GetFileInfo(string path)
|
||||
{
|
||||
path = path.ToLowerInvariant().Replace('\\', '/');
|
||||
|
||||
var (fileType, objectType, match) = ParseGamePath(path);
|
||||
if (match == null || !match.Success)
|
||||
return new GameObjectInfo
|
||||
{
|
||||
FileType = fileType,
|
||||
ObjectType = objectType,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var groups = match.Groups;
|
||||
switch (objectType)
|
||||
{
|
||||
case ObjectType.Accessory: return HandleEquipment(fileType, groups);
|
||||
case ObjectType.Equipment: return HandleEquipment(fileType, groups);
|
||||
case ObjectType.Weapon: return HandleWeapon(fileType, groups);
|
||||
case ObjectType.Map: return HandleMap(fileType, groups);
|
||||
case ObjectType.Monster: return HandleMonster(fileType, groups);
|
||||
case ObjectType.DemiHuman: return HandleDemiHuman(fileType, groups);
|
||||
case ObjectType.Character: return HandleCustomization(fileType, groups);
|
||||
case ObjectType.Icon: return HandleIcon(fileType, groups);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Could not parse {path}:\n{e}");
|
||||
}
|
||||
|
||||
return new GameObjectInfo
|
||||
{
|
||||
FileType = fileType,
|
||||
ObjectType = objectType,
|
||||
};
|
||||
}
|
||||
|
||||
public string VfxToKey(string path)
|
||||
{
|
||||
var match = _vfxRegexTmb.Match(path);
|
||||
if (match.Success)
|
||||
return match.Groups["key"].Value.ToLowerInvariant();
|
||||
|
||||
match = _vfxRegexPap.Match(path);
|
||||
return match.Success ? match.Groups["key"].Value.ToLowerInvariant() : string.Empty;
|
||||
}
|
||||
|
||||
private const string CharacterFolder = "chara";
|
||||
private const string EquipmentFolder = "equipment";
|
||||
private const string PlayerFolder = "human";
|
||||
|
|
@ -30,62 +80,74 @@ internal class GamePathParser : IGamePathParser
|
|||
private const string WorldFolder1 = "bgcommon";
|
||||
private const string WorldFolder2 = "bg";
|
||||
|
||||
// @formatter:off
|
||||
private readonly Dictionary<FileType, Dictionary<ObjectType, Regex[]>> _regexes = new()
|
||||
{ { FileType.Font, new Dictionary< ObjectType, Regex[] >(){ { ObjectType.Font, new Regex[]{ new(@"common/font/(?'fontname'.*)_(?'id'\d\d)(_lobby)?\.fdt") } } } }
|
||||
, { FileType.Texture, new Dictionary< ObjectType, Regex[] >()
|
||||
{ { ObjectType.Icon, new Regex[]{ new(@"ui/icon/(?'group'\d*)(/(?'lang'[a-z]{2}))?(/(?'hq'hq))?/(?'id'\d*)(?'hr'_hr1)?\.tex") } }
|
||||
, { ObjectType.Map, new Regex[]{ new(@"ui/map/(?'id'[a-z0-9]{4})/(?'variant'\d{2})/\k'id'\k'variant'(?'suffix'[a-z])?(_[a-z])?\.tex") } }
|
||||
, { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/texture/v(?'variant'\d{2})_w\k'id'b\k'weapon'(_[a-z])?_[a-z]\.tex") } }
|
||||
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/texture/v(?'variant'\d{2})_m\k'monster'b\k'id'(_[a-z])?_[a-z]\.tex") } }
|
||||
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/texture/v(?'variant'\d{2})_c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})(_[a-z])?_[a-z]\.tex") } }
|
||||
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/texture/v(?'variant'\d{2})_d\k'id'e\k'equip'_(?'slot'[a-z]{3})(_[a-z])?_[a-z]\.tex") } }
|
||||
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/texture/v(?'variant'\d{2})_c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})_[a-z]\.tex") } }
|
||||
, { ObjectType.Character, new Regex[]{ new(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/texture/(?'minus'(--)?)(v(?'variant'\d{2})_)?c\k'race'\k'typeabr'\k'id'(_(?'slot'[a-z]{3}))?(_[a-z])?_[a-z]\.tex")
|
||||
, new(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/texture")
|
||||
, new(@"chara/common/texture/skin(?'skin'.*)\.tex")
|
||||
, new(@"chara/common/texture/(?'catchlight'catchlight)(.*)\.tex")
|
||||
, new(@"chara/common/texture/decal_(?'location'[a-z]+)/[-_]?decal_(?'id'\d+).tex") } } } }
|
||||
, { FileType.Model, new Dictionary< ObjectType, Regex[] >()
|
||||
{ { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/model/w\k'id'b\k'weapon'\.mdl") } }
|
||||
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/model/m\k'monster'b\k'id'\.mdl") } }
|
||||
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/model/c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})\.mdl") } }
|
||||
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/model/d\k'id'e\k'equip'_(?'slot'[a-z]{3})\.mdl") } }
|
||||
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/model/c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})\.mdl") } }
|
||||
, { ObjectType.Character, new Regex[]{ new(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/model/c\k'race'\k'typeabr'\k'id'_(?'slot'[a-z]{3})\.mdl") } } } }
|
||||
, { FileType.Material, new Dictionary< ObjectType, Regex[] >()
|
||||
{ { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/material/v(?'variant'\d{4})/mt_w\k'id'b\k'weapon'_[a-z]\.mtrl") } }
|
||||
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/material/v(?'variant'\d{4})/mt_m\k'monster'b\k'id'_[a-z]\.mtrl") } }
|
||||
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})_[a-z]\.mtrl") } }
|
||||
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/material/v(?'variant'\d{4})/mt_d\k'id'e\k'equip'_(?'slot'[a-z]{3})_[a-z]\.mtrl") } }
|
||||
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})_[a-z]\.mtrl") } }
|
||||
, { ObjectType.Character, new Regex[]{ new( @"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/material(/v(?'variant'\d{4}))?/mt_c\k'race'\k'typeabr'\k'id'(_(?'slot'[a-z]{3}))?_[a-z]\.mtrl" ) } } } }
|
||||
, { FileType.Imc, new Dictionary< ObjectType, Regex[] >()
|
||||
{ { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/b\k'weapon'\.imc") } }
|
||||
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/b\k'id'\.imc") } }
|
||||
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/e\k'id'\.imc") } }
|
||||
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/e\k'equip'\.imc") } }
|
||||
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/a\k'id'\.imc") } } } },
|
||||
};
|
||||
// @formatter:off
|
||||
// language=regex
|
||||
private readonly IReadOnlyDictionary<FileType, IReadOnlyDictionary<ObjectType, IReadOnlyList<Regex>>> _regexes = new Dictionary<FileType, IReadOnlyDictionary<ObjectType, IReadOnlyList<Regex>>>()
|
||||
{
|
||||
[FileType.Font] = new Dictionary<ObjectType, IReadOnlyList<Regex>>
|
||||
{
|
||||
[ObjectType.Font] = CreateRegexes( @"common/font/(?'fontname'.*)_(?'id'\d\d)(_lobby)?\.fdt"),
|
||||
},
|
||||
[FileType.Texture] = new Dictionary<ObjectType, IReadOnlyList<Regex>>
|
||||
{
|
||||
[ObjectType.Icon] = CreateRegexes( @"ui/icon/(?'group'\d*)(/(?'lang'[a-z]{2}))?(/(?'hq'hq))?/(?'id'\d*)(?'hr'_hr1)?\.tex"),
|
||||
[ObjectType.Map] = CreateRegexes( @"ui/map/(?'id'[a-z0-9]{4})/(?'variant'\d{2})/\k'id'\k'variant'(?'suffix'[a-z])?(_[a-z])?\.tex"),
|
||||
[ObjectType.Weapon] = CreateRegexes( @"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/texture/v(?'variant'\d{2})_w\k'id'b\k'weapon'(_[a-z])?_[a-z]\.tex"),
|
||||
[ObjectType.Monster] = CreateRegexes( @"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/texture/v(?'variant'\d{2})_m\k'monster'b\k'id'(_[a-z])?_[a-z]\.tex"),
|
||||
[ObjectType.Equipment] = CreateRegexes( @"chara/equipment/e(?'id'\d{4})/texture/v(?'variant'\d{2})_c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})(_[a-z])?_[a-z]\.tex"),
|
||||
[ObjectType.DemiHuman] = CreateRegexes( @"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/texture/v(?'variant'\d{2})_d\k'id'e\k'equip'_(?'slot'[a-z]{3})(_[a-z])?_[a-z]\.tex"),
|
||||
[ObjectType.Accessory] = CreateRegexes( @"chara/accessory/a(?'id'\d{4})/texture/v(?'variant'\d{2})_c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})_[a-z]\.tex"),
|
||||
[ObjectType.Character] = CreateRegexes( @"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/texture/(?'minus'(--)?)(v(?'variant'\d{2})_)?c\k'race'\k'typeabr'\k'id'(_(?'slot'[a-z]{3}))?(_[a-z])?_[a-z]\.tex"
|
||||
, @"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/texture"
|
||||
, @"chara/common/texture/skin(?'skin'.*)\.tex"
|
||||
, @"chara/common/texture/(?'catchlight'catchlight)(.*)\.tex"
|
||||
, @"chara/common/texture/decal_(?'location'[a-z]+)/[-_]?decal_(?'id'\d+).tex"),
|
||||
},
|
||||
[FileType.Model] = new Dictionary<ObjectType, IReadOnlyList<Regex>>
|
||||
{
|
||||
[ObjectType.Weapon] = CreateRegexes(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/model/w\k'id'b\k'weapon'\.mdl"),
|
||||
[ObjectType.Monster] = CreateRegexes(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/model/m\k'monster'b\k'id'\.mdl"),
|
||||
[ObjectType.Equipment] = CreateRegexes(@"chara/equipment/e(?'id'\d{4})/model/c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})\.mdl"),
|
||||
[ObjectType.DemiHuman] = CreateRegexes(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/model/d\k'id'e\k'equip'_(?'slot'[a-z]{3})\.mdl"),
|
||||
[ObjectType.Accessory] = CreateRegexes(@"chara/accessory/a(?'id'\d{4})/model/c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})\.mdl"),
|
||||
[ObjectType.Character] = CreateRegexes(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/model/c\k'race'\k'typeabr'\k'id'_(?'slot'[a-z]{3})\.mdl"),
|
||||
},
|
||||
[FileType.Material] = new Dictionary<ObjectType, IReadOnlyList<Regex>>
|
||||
{
|
||||
[ObjectType.Weapon] = CreateRegexes(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/material/v(?'variant'\d{4})/mt_w\k'id'b\k'weapon'_[a-z]\.mtrl"),
|
||||
[ObjectType.Monster] = CreateRegexes(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/material/v(?'variant'\d{4})/mt_m\k'monster'b\k'id'_[a-z]\.mtrl"),
|
||||
[ObjectType.Equipment] = CreateRegexes(@"chara/equipment/e(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})_[a-z]\.mtrl"),
|
||||
[ObjectType.DemiHuman] = CreateRegexes(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/material/v(?'variant'\d{4})/mt_d\k'id'e\k'equip'_(?'slot'[a-z]{3})_[a-z]\.mtrl"),
|
||||
[ObjectType.Accessory] = CreateRegexes(@"chara/accessory/a(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})_[a-z]\.mtrl"),
|
||||
[ObjectType.Character] = CreateRegexes(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/material(/v(?'variant'\d{4}))?/mt_c\k'race'\k'typeabr'\k'id'(_(?'slot'[a-z]{3}))?_[a-z]\.mtrl"),
|
||||
},
|
||||
[FileType.Imc] = new Dictionary<ObjectType, IReadOnlyList<Regex>>
|
||||
{
|
||||
[ObjectType.Weapon] = CreateRegexes(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/b\k'weapon'\.imc"),
|
||||
[ObjectType.Monster] = CreateRegexes(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/b\k'id'\.imc"),
|
||||
[ObjectType.Equipment] = CreateRegexes(@"chara/equipment/e(?'id'\d{4})/e\k'id'\.imc"),
|
||||
[ObjectType.DemiHuman] = CreateRegexes(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/e\k'equip'\.imc"),
|
||||
[ObjectType.Accessory] = CreateRegexes(@"chara/accessory/a(?'id'\d{4})/a\k'id'\.imc"),
|
||||
},
|
||||
};
|
||||
// @formatter:on
|
||||
|
||||
public ObjectType PathToObjectType( GamePath path )
|
||||
|
||||
private static IReadOnlyList<Regex> CreateRegexes(params string[] regexes)
|
||||
=> regexes.Select(s => new Regex(s, RegexOptions.Compiled)).ToArray();
|
||||
|
||||
public ObjectType PathToObjectType(string path)
|
||||
{
|
||||
if( path.Empty )
|
||||
{
|
||||
if (path.Length == 0)
|
||||
return ObjectType.Unknown;
|
||||
}
|
||||
|
||||
string p = path;
|
||||
var folders = p.Split( '/' );
|
||||
if( folders.Length < 2 )
|
||||
{
|
||||
var folders = path.Split('/');
|
||||
if (folders.Length < 2)
|
||||
return ObjectType.Unknown;
|
||||
}
|
||||
|
||||
return folders[ 0 ] switch
|
||||
return folders[0] switch
|
||||
{
|
||||
CharacterFolder => folders[ 1 ] switch
|
||||
CharacterFolder => folders[1] switch
|
||||
{
|
||||
EquipmentFolder => ObjectType.Equipment,
|
||||
AccessoryFolder => ObjectType.Accessory,
|
||||
|
|
@ -96,7 +158,7 @@ internal class GamePathParser : IGamePathParser
|
|||
CommonFolder => ObjectType.Character,
|
||||
_ => ObjectType.Unknown,
|
||||
},
|
||||
UiFolder => folders[ 1 ] switch
|
||||
UiFolder => folders[1] switch
|
||||
{
|
||||
IconFolder => ObjectType.Icon,
|
||||
LoadingFolder => ObjectType.LoadingScreen,
|
||||
|
|
@ -104,13 +166,13 @@ internal class GamePathParser : IGamePathParser
|
|||
InterfaceFolder => ObjectType.Interface,
|
||||
_ => ObjectType.Unknown,
|
||||
},
|
||||
CommonFolder => folders[ 1 ] switch
|
||||
CommonFolder => folders[1] switch
|
||||
{
|
||||
FontFolder => ObjectType.Font,
|
||||
_ => ObjectType.Unknown,
|
||||
},
|
||||
HousingFolder => ObjectType.Housing,
|
||||
WorldFolder1 => folders[ 1 ] switch
|
||||
WorldFolder1 => folders[1] switch
|
||||
{
|
||||
HousingFolder => ObjectType.Housing,
|
||||
_ => ObjectType.World,
|
||||
|
|
@ -121,152 +183,120 @@ internal class GamePathParser : IGamePathParser
|
|||
};
|
||||
}
|
||||
|
||||
private (FileType, ObjectType, Match?) ParseGamePath( GamePath path )
|
||||
private (FileType, ObjectType, Match?) ParseGamePath(string path)
|
||||
{
|
||||
if( !Names.ExtensionToFileType.TryGetValue( Extension( path ), out var fileType ) )
|
||||
{
|
||||
if (!Names.ExtensionToFileType.TryGetValue(Path.GetExtension(path), out var fileType))
|
||||
fileType = FileType.Unknown;
|
||||
}
|
||||
|
||||
var objectType = PathToObjectType( path );
|
||||
var objectType = PathToObjectType(path);
|
||||
|
||||
if( !_regexes.TryGetValue( fileType, out var objectDict ) )
|
||||
if (!_regexes.TryGetValue(fileType, out var objectDict))
|
||||
return (fileType, objectType, null);
|
||||
|
||||
if (!objectDict.TryGetValue(objectType, out var regexes))
|
||||
return (fileType, objectType, null);
|
||||
|
||||
foreach (var regex in regexes)
|
||||
{
|
||||
return ( fileType, objectType, null );
|
||||
var match = regex.Match(path);
|
||||
if (match.Success)
|
||||
return (fileType, objectType, match);
|
||||
}
|
||||
|
||||
if( !objectDict.TryGetValue( objectType, out var regexes ) )
|
||||
{
|
||||
return ( fileType, objectType, null );
|
||||
}
|
||||
|
||||
foreach( var regex in regexes )
|
||||
{
|
||||
var match = regex.Match( path );
|
||||
if( match.Success )
|
||||
{
|
||||
return ( fileType, objectType, match );
|
||||
}
|
||||
}
|
||||
|
||||
return ( fileType, objectType, null );
|
||||
return (fileType, objectType, null);
|
||||
}
|
||||
|
||||
private static string Extension( string filename )
|
||||
private static GameObjectInfo HandleEquipment(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
var extIdx = filename.LastIndexOf( '.' );
|
||||
return extIdx < 0 ? "" : filename.Substring( extIdx );
|
||||
var setId = ushort.Parse(groups["id"].Value);
|
||||
if (fileType == FileType.Imc)
|
||||
return GameObjectInfo.Equipment(fileType, setId);
|
||||
|
||||
var gr = Names.GenderRaceFromCode(groups["race"].Value);
|
||||
var slot = Names.SuffixToEquipSlot[groups["slot"].Value];
|
||||
if (fileType == FileType.Model)
|
||||
return GameObjectInfo.Equipment(fileType, setId, gr, slot);
|
||||
|
||||
var variant = byte.Parse(groups["variant"].Value);
|
||||
return GameObjectInfo.Equipment(fileType, setId, gr, slot, variant);
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleEquipment( FileType fileType, GroupCollection groups )
|
||||
private static GameObjectInfo HandleWeapon(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
var setId = ushort.Parse( groups[ "id" ].Value );
|
||||
if( fileType == FileType.Imc )
|
||||
{
|
||||
return GameObjectInfo.Equipment( fileType, setId );
|
||||
}
|
||||
var weaponId = ushort.Parse(groups["weapon"].Value);
|
||||
var setId = ushort.Parse(groups["id"].Value);
|
||||
if (fileType == FileType.Imc || fileType == FileType.Model)
|
||||
return GameObjectInfo.Weapon(fileType, setId, weaponId);
|
||||
|
||||
var gr = Names.GenderRaceFromCode( groups[ "race" ].Value );
|
||||
var slot = Names.SuffixToEquipSlot[ groups[ "slot" ].Value ];
|
||||
if( fileType == FileType.Model )
|
||||
{
|
||||
return GameObjectInfo.Equipment( fileType, setId, gr, slot );
|
||||
}
|
||||
|
||||
var variant = byte.Parse( groups[ "variant" ].Value );
|
||||
return GameObjectInfo.Equipment( fileType, setId, gr, slot, variant );
|
||||
var variant = byte.Parse(groups["variant"].Value);
|
||||
return GameObjectInfo.Weapon(fileType, setId, weaponId, variant);
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleWeapon( FileType fileType, GroupCollection groups )
|
||||
private static GameObjectInfo HandleMonster(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
var weaponId = ushort.Parse( groups[ "weapon" ].Value );
|
||||
var setId = ushort.Parse( groups[ "id" ].Value );
|
||||
if( fileType == FileType.Imc || fileType == FileType.Model )
|
||||
{
|
||||
return GameObjectInfo.Weapon( fileType, setId, weaponId );
|
||||
}
|
||||
var monsterId = ushort.Parse(groups["monster"].Value);
|
||||
var bodyId = ushort.Parse(groups["id"].Value);
|
||||
if (fileType == FileType.Imc || fileType == FileType.Model)
|
||||
return GameObjectInfo.Monster(fileType, monsterId, bodyId);
|
||||
|
||||
var variant = byte.Parse( groups[ "variant" ].Value );
|
||||
return GameObjectInfo.Weapon( fileType, setId, weaponId, variant );
|
||||
var variant = byte.Parse(groups["variant"].Value);
|
||||
return GameObjectInfo.Monster(fileType, monsterId, bodyId, variant);
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleMonster( FileType fileType, GroupCollection groups )
|
||||
private static GameObjectInfo HandleDemiHuman(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
var monsterId = ushort.Parse( groups[ "monster" ].Value );
|
||||
var bodyId = ushort.Parse( groups[ "id" ].Value );
|
||||
if( fileType == FileType.Imc || fileType == FileType.Model )
|
||||
{
|
||||
return GameObjectInfo.Monster( fileType, monsterId, bodyId );
|
||||
}
|
||||
var demiHumanId = ushort.Parse(groups["id"].Value);
|
||||
var equipId = ushort.Parse(groups["equip"].Value);
|
||||
if (fileType == FileType.Imc)
|
||||
return GameObjectInfo.DemiHuman(fileType, demiHumanId, equipId);
|
||||
|
||||
var variant = byte.Parse( groups[ "variant" ].Value );
|
||||
return GameObjectInfo.Monster( fileType, monsterId, bodyId, variant );
|
||||
var slot = Names.SuffixToEquipSlot[groups["slot"].Value];
|
||||
if (fileType == FileType.Model)
|
||||
return GameObjectInfo.DemiHuman(fileType, demiHumanId, equipId, slot);
|
||||
|
||||
var variant = byte.Parse(groups["variant"].Value);
|
||||
return GameObjectInfo.DemiHuman(fileType, demiHumanId, equipId, slot, variant);
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleDemiHuman( FileType fileType, GroupCollection groups )
|
||||
private static GameObjectInfo HandleCustomization(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
var demiHumanId = ushort.Parse( groups[ "id" ].Value );
|
||||
var equipId = ushort.Parse( groups[ "equip" ].Value );
|
||||
if( fileType == FileType.Imc )
|
||||
if (groups["catchlight"].Success)
|
||||
return GameObjectInfo.Customization(fileType, CustomizationType.Iris);
|
||||
|
||||
if (groups["skin"].Success)
|
||||
return GameObjectInfo.Customization(fileType, CustomizationType.Skin);
|
||||
|
||||
var id = ushort.Parse(groups["id"].Value);
|
||||
if (groups["location"].Success)
|
||||
{
|
||||
return GameObjectInfo.DemiHuman( fileType, demiHumanId, equipId );
|
||||
var tmpType = groups["location"].Value == "face" ? CustomizationType.DecalFace
|
||||
: groups["location"].Value == "equip" ? CustomizationType.DecalEquip : CustomizationType.Unknown;
|
||||
return GameObjectInfo.Customization(fileType, tmpType, id);
|
||||
}
|
||||
|
||||
var slot = Names.SuffixToEquipSlot[ groups[ "slot" ].Value ];
|
||||
if( fileType == FileType.Model )
|
||||
{
|
||||
return GameObjectInfo.DemiHuman( fileType, demiHumanId, equipId, slot );
|
||||
}
|
||||
|
||||
var variant = byte.Parse( groups[ "variant" ].Value );
|
||||
return GameObjectInfo.DemiHuman( fileType, demiHumanId, equipId, slot, variant );
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleCustomization( FileType fileType, GroupCollection groups )
|
||||
{
|
||||
if( groups[ "catchlight" ].Success )
|
||||
{
|
||||
return GameObjectInfo.Customization( fileType, CustomizationType.Iris );
|
||||
}
|
||||
|
||||
if( groups[ "skin" ].Success )
|
||||
{
|
||||
return GameObjectInfo.Customization( fileType, CustomizationType.Skin );
|
||||
}
|
||||
|
||||
var id = ushort.Parse( groups[ "id" ].Value );
|
||||
if( groups[ "location" ].Success )
|
||||
{
|
||||
var tmpType = groups[ "location" ].Value == "face" ? CustomizationType.DecalFace
|
||||
: groups[ "location" ].Value == "equip" ? CustomizationType.DecalEquip : CustomizationType.Unknown;
|
||||
return GameObjectInfo.Customization( fileType, tmpType, id );
|
||||
}
|
||||
|
||||
var gr = Names.GenderRaceFromCode( groups[ "race" ].Value );
|
||||
var bodySlot = Names.StringToBodySlot[ groups[ "type" ].Value ];
|
||||
var type = groups[ "slot" ].Success
|
||||
? Names.SuffixToCustomizationType[ groups[ "slot" ].Value ]
|
||||
var gr = Names.GenderRaceFromCode(groups["race"].Value);
|
||||
var bodySlot = Names.StringToBodySlot[groups["type"].Value];
|
||||
var type = groups["slot"].Success
|
||||
? Names.SuffixToCustomizationType[groups["slot"].Value]
|
||||
: CustomizationType.Skin;
|
||||
if( fileType == FileType.Material )
|
||||
if (fileType == FileType.Material)
|
||||
{
|
||||
var variant = groups[ "variant" ].Success ? byte.Parse( groups[ "variant" ].Value ) : ( byte )0;
|
||||
return GameObjectInfo.Customization( fileType, type, id, gr, bodySlot, variant );
|
||||
var variant = groups["variant"].Success ? byte.Parse(groups["variant"].Value) : (byte)0;
|
||||
return GameObjectInfo.Customization(fileType, type, id, gr, bodySlot, variant);
|
||||
}
|
||||
|
||||
return GameObjectInfo.Customization( fileType, type, id, gr, bodySlot );
|
||||
return GameObjectInfo.Customization(fileType, type, id, gr, bodySlot);
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleIcon( FileType fileType, GroupCollection groups )
|
||||
private static GameObjectInfo HandleIcon(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
var hq = groups[ "hq" ].Success;
|
||||
var hr = groups[ "hr" ].Success;
|
||||
var id = uint.Parse( groups[ "id" ].Value );
|
||||
if( !groups[ "lang" ].Success )
|
||||
{
|
||||
return GameObjectInfo.Icon( fileType, id, hq, hr );
|
||||
}
|
||||
var hq = groups["hq"].Success;
|
||||
var hr = groups["hr"].Success;
|
||||
var id = uint.Parse(groups["id"].Value);
|
||||
if (!groups["lang"].Success)
|
||||
return GameObjectInfo.Icon(fileType, id, hq, hr);
|
||||
|
||||
var language = groups[ "lang" ].Value switch
|
||||
var language = groups["lang"].Value switch
|
||||
{
|
||||
"en" => Dalamud.ClientLanguage.English,
|
||||
"ja" => Dalamud.ClientLanguage.Japanese,
|
||||
|
|
@ -274,65 +304,23 @@ internal class GamePathParser : IGamePathParser
|
|||
"fr" => Dalamud.ClientLanguage.French,
|
||||
_ => Dalamud.ClientLanguage.English,
|
||||
};
|
||||
return GameObjectInfo.Icon( fileType, id, hq, hr, language );
|
||||
return GameObjectInfo.Icon(fileType, id, hq, hr, language);
|
||||
}
|
||||
|
||||
private static GameObjectInfo HandleMap( FileType fileType, GroupCollection groups )
|
||||
private static GameObjectInfo HandleMap(FileType fileType, GroupCollection groups)
|
||||
{
|
||||
var map = Encoding.ASCII.GetBytes( groups[ "id" ].Value );
|
||||
var variant = byte.Parse( groups[ "variant" ].Value );
|
||||
if( groups[ "suffix" ].Success )
|
||||
var map = Encoding.ASCII.GetBytes(groups["id"].Value);
|
||||
var variant = byte.Parse(groups["variant"].Value);
|
||||
if (groups["suffix"].Success)
|
||||
{
|
||||
var suffix = Encoding.ASCII.GetBytes( groups[ "suffix" ].Value )[ 0 ];
|
||||
return GameObjectInfo.Map( fileType, map[ 0 ], map[ 1 ], map[ 2 ], map[ 3 ], variant, suffix );
|
||||
var suffix = Encoding.ASCII.GetBytes(groups["suffix"].Value)[0];
|
||||
return GameObjectInfo.Map(fileType, map[0], map[1], map[2], map[3], variant, suffix);
|
||||
}
|
||||
|
||||
return GameObjectInfo.Map( fileType, map[ 0 ], map[ 1 ], map[ 2 ], map[ 3 ], variant );
|
||||
return GameObjectInfo.Map(fileType, map[0], map[1], map[2], map[3], variant);
|
||||
}
|
||||
|
||||
public GameObjectInfo GetFileInfo( GamePath path )
|
||||
{
|
||||
var (fileType, objectType, match) = ParseGamePath( path );
|
||||
if( match == null || !match.Success )
|
||||
{
|
||||
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var groups = match.Groups;
|
||||
switch( objectType )
|
||||
{
|
||||
case ObjectType.Accessory: return HandleEquipment( fileType, groups );
|
||||
case ObjectType.Equipment: return HandleEquipment( fileType, groups );
|
||||
case ObjectType.Weapon: return HandleWeapon( fileType, groups );
|
||||
case ObjectType.Map: return HandleMap( fileType, groups );
|
||||
case ObjectType.Monster: return HandleMonster( fileType, groups );
|
||||
case ObjectType.DemiHuman: return HandleDemiHuman( fileType, groups );
|
||||
case ObjectType.Character: return HandleCustomization( fileType, groups );
|
||||
case ObjectType.Icon: return HandleIcon( fileType, groups );
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not parse {path}:\n{e}" );
|
||||
}
|
||||
|
||||
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
|
||||
}
|
||||
|
||||
private readonly Regex _vfxRegexTmb = new(@"chara/action/(?'key'[^\s]+?)\.tmb");
|
||||
private readonly Regex _vfxRegexPap = new(@"chara/human/c0101/animation/a0001/[^\s]+?/(?'key'[^\s]+?)\.pap");
|
||||
|
||||
public string VfxToKey( GamePath path )
|
||||
{
|
||||
var match = _vfxRegexTmb.Match( path );
|
||||
if( match.Success )
|
||||
{
|
||||
return match.Groups[ "key" ].Value.ToLowerInvariant();
|
||||
}
|
||||
|
||||
match = _vfxRegexPap.Match( path );
|
||||
return match.Success ? match.Groups[ "key" ].Value.ToLowerInvariant() : string.Empty;
|
||||
}
|
||||
}
|
||||
private readonly Regex _vfxRegexTmb = new(@"chara[\/]action[\/](?'key'[^\s]+?)\.tmb", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Regex _vfxRegexPap = new(@"chara[\/]human[\/]c0101[\/]animation[\/]a0001[\/][^\s]+?[\/](?'key'[^\s]+?)\.pap", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,101 +1,193 @@
|
|||
using System;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.GameData.Util;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Utility;
|
||||
using Action = Lumina.Excel.GeneratedSheets.Action;
|
||||
|
||||
namespace Penumbra.GameData;
|
||||
|
||||
internal class ObjectIdentification : IObjectIdentifier
|
||||
{
|
||||
public static DataManager? DataManager = null!;
|
||||
private readonly List< (ulong, HashSet< Item >) > _weapons;
|
||||
private readonly List< (ulong, HashSet< Item >) > _equipment;
|
||||
private readonly Dictionary< string, HashSet< Action > > _actions;
|
||||
public IGamePathParser GamePathParser { get; } = new GamePathParser();
|
||||
|
||||
private static bool Add( IDictionary< ulong, HashSet< Item > > dict, ulong key, Item item )
|
||||
public void Identify(IDictionary<string, object?> set, string path)
|
||||
{
|
||||
if( dict.TryGetValue( key, out var list ) )
|
||||
if (path.EndsWith(".pap", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".tmb", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return list.Add( item );
|
||||
}
|
||||
|
||||
dict[ key ] = new HashSet< Item > { item };
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ulong EquipmentKey( Item i )
|
||||
{
|
||||
var model = ( ulong )( ( Lumina.Data.Parsing.Quad )i.ModelMain ).A;
|
||||
var variant = ( ulong )( ( Lumina.Data.Parsing.Quad )i.ModelMain ).B;
|
||||
var slot = ( ulong )( ( EquipSlot )i.EquipSlotCategory.Row ).ToSlot();
|
||||
return ( model << 32 ) | ( slot << 16 ) | variant;
|
||||
}
|
||||
|
||||
private static ulong WeaponKey( Item i, bool offhand )
|
||||
{
|
||||
var quad = offhand ? ( Lumina.Data.Parsing.Quad )i.ModelSub : ( Lumina.Data.Parsing.Quad )i.ModelMain;
|
||||
var model = ( ulong )quad.A;
|
||||
var type = ( ulong )quad.B;
|
||||
var variant = ( ulong )quad.C;
|
||||
|
||||
return ( model << 32 ) | ( type << 16 ) | variant;
|
||||
}
|
||||
|
||||
private void AddAction( string key, Action action )
|
||||
{
|
||||
if( key.Length == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
key = key.ToLowerInvariant();
|
||||
if( _actions.TryGetValue( key, out var actions ) )
|
||||
{
|
||||
actions.Add( action );
|
||||
IdentifyVfx(set, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
_actions[ key ] = new HashSet< Action > { action };
|
||||
var info = GamePathParser.GetFileInfo(path);
|
||||
IdentifyParsed(set, info);
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectIdentification( DataManager dataManager, ClientLanguage clientLanguage )
|
||||
public Dictionary<string, object?> Identify(string path)
|
||||
{
|
||||
DataManager = dataManager;
|
||||
var items = dataManager.GetExcelSheet< Item >( clientLanguage )!;
|
||||
SortedList< ulong, HashSet< Item > > weapons = new();
|
||||
SortedList< ulong, HashSet< Item > > equipment = new();
|
||||
foreach( var item in items )
|
||||
Dictionary<string, object?> ret = new();
|
||||
Identify(ret, path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public IReadOnlyList<Item>? Identify(SetId setId, WeaponType weaponType, ushort variant, EquipSlot slot)
|
||||
{
|
||||
switch (slot)
|
||||
{
|
||||
switch( ( EquipSlot )item.EquipSlotCategory.Row )
|
||||
case EquipSlot.MainHand:
|
||||
case EquipSlot.OffHand:
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
case EquipSlot.OffHand:
|
||||
case EquipSlot.BothHand:
|
||||
if( item.ModelMain != 0 )
|
||||
{
|
||||
Add( weapons, WeaponKey( item, false ), item );
|
||||
}
|
||||
var (begin, _) = FindIndexRange((List<(ulong, IReadOnlyList<Item>)>)_weapons,
|
||||
((ulong)setId << 32) | ((ulong)weaponType << 16) | variant,
|
||||
0xFFFFFFFFFFFF);
|
||||
return begin >= 0 ? _weapons[begin].Item2 : null;
|
||||
}
|
||||
default:
|
||||
{
|
||||
var (begin, _) = FindIndexRange((List<(ulong, IReadOnlyList<Item>)>)_equipment,
|
||||
((ulong)setId << 32) | ((ulong)slot.ToSlot() << 16) | variant,
|
||||
0xFFFFFFFFFFFF);
|
||||
return begin >= 0 ? _equipment[begin].Item2 : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( item.ModelSub != 0 )
|
||||
{
|
||||
Add( weapons, WeaponKey( item, true ), item );
|
||||
}
|
||||
private const int Version = 1;
|
||||
|
||||
break;
|
||||
private readonly DataManager _dataManager;
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly ClientLanguage _language;
|
||||
|
||||
private readonly IReadOnlyList<(ulong Key, IReadOnlyList<Item> Values)> _weapons;
|
||||
private readonly IReadOnlyList<(ulong Key, IReadOnlyList<Item> Values)> _equipment;
|
||||
private readonly IReadOnlyDictionary<string, IReadOnlyList<Action>> _actions;
|
||||
|
||||
private readonly string _weaponsTag;
|
||||
private readonly string _equipmentTag;
|
||||
private readonly string _actionsTag;
|
||||
private bool _disposed = false;
|
||||
|
||||
public ObjectIdentification(DalamudPluginInterface pluginInterface, DataManager dataManager, ClientLanguage language)
|
||||
{
|
||||
_pluginInterface = pluginInterface;
|
||||
_dataManager = dataManager;
|
||||
_language = language;
|
||||
|
||||
_weaponsTag = $"Penumbra.Identification.Weapons.{_language}.V{Version}";
|
||||
_equipmentTag = $"Penumbra.Identification.Equipment.{_language}.V{Version}";
|
||||
_actionsTag = $"Penumbra.Identification.Actions.{_language}.V{Version}";
|
||||
|
||||
try
|
||||
{
|
||||
_weapons = pluginInterface.GetOrCreateData(_weaponsTag, CreateWeaponList);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error($"Error creating shared identification data for weapons:\n{ex}");
|
||||
_weapons = CreateWeaponList();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_equipment = pluginInterface.GetOrCreateData(_equipmentTag, CreateEquipmentList);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error($"Error creating shared identification data for equipment:\n{ex}");
|
||||
_equipment = CreateEquipmentList();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_actions = pluginInterface.GetOrCreateData(_actionsTag, CreateActionList);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error($"Error creating shared identification data for actions:\n{ex}");
|
||||
_actions = CreateActionList();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
_pluginInterface.RelinquishData(_weaponsTag);
|
||||
_pluginInterface.RelinquishData(_equipmentTag);
|
||||
_pluginInterface.RelinquishData(_actionsTag);
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
~ObjectIdentification()
|
||||
=> Dispose();
|
||||
|
||||
private static bool Add(IDictionary<ulong, HashSet<Item>> dict, ulong key, Item item)
|
||||
{
|
||||
if (dict.TryGetValue(key, out var list))
|
||||
return list.Add(item);
|
||||
|
||||
dict[key] = new HashSet<Item> { item };
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ulong EquipmentKey(Item i)
|
||||
{
|
||||
var model = (ulong)((Lumina.Data.Parsing.Quad)i.ModelMain).A;
|
||||
var variant = (ulong)((Lumina.Data.Parsing.Quad)i.ModelMain).B;
|
||||
var slot = (ulong)((EquipSlot)i.EquipSlotCategory.Row).ToSlot();
|
||||
return (model << 32) | (slot << 16) | variant;
|
||||
}
|
||||
|
||||
private static ulong WeaponKey(Item i, bool offhand)
|
||||
{
|
||||
var quad = offhand ? (Lumina.Data.Parsing.Quad)i.ModelSub : (Lumina.Data.Parsing.Quad)i.ModelMain;
|
||||
var model = (ulong)quad.A;
|
||||
var type = (ulong)quad.B;
|
||||
var variant = (ulong)quad.C;
|
||||
|
||||
return (model << 32) | (type << 16) | variant;
|
||||
}
|
||||
|
||||
private IReadOnlyList<(ulong Key, IReadOnlyList<Item> Values)> CreateWeaponList()
|
||||
{
|
||||
var items = _dataManager.GetExcelSheet<Item>(_language)!;
|
||||
var storage = new SortedList<ulong, HashSet<Item>>();
|
||||
foreach (var item in items.Where(i
|
||||
=> (EquipSlot)i.EquipSlotCategory.Row is EquipSlot.MainHand or EquipSlot.OffHand or EquipSlot.BothHand))
|
||||
{
|
||||
if (item.ModelMain != 0)
|
||||
Add(storage, WeaponKey(item, false), item);
|
||||
|
||||
if (item.ModelSub != 0)
|
||||
Add(storage, WeaponKey(item, true), item);
|
||||
}
|
||||
|
||||
return storage.Select(kvp => (kvp.Key, (IReadOnlyList<Item>)kvp.Value.ToArray())).ToList();
|
||||
}
|
||||
|
||||
private IReadOnlyList<(ulong Key, IReadOnlyList<Item> Values)> CreateEquipmentList()
|
||||
{
|
||||
var items = _dataManager.GetExcelSheet<Item>(_language)!;
|
||||
var storage = new SortedList<ulong, HashSet<Item>>();
|
||||
foreach (var item in items)
|
||||
{
|
||||
switch (((EquipSlot)item.EquipSlotCategory.Row).ToSlot())
|
||||
{
|
||||
// Accessories
|
||||
case EquipSlot.RFinger:
|
||||
case EquipSlot.Wrists:
|
||||
case EquipSlot.Ears:
|
||||
case EquipSlot.Neck:
|
||||
Add( equipment, EquipmentKey( item ), item );
|
||||
break;
|
||||
// Equipment
|
||||
case EquipSlot.Head:
|
||||
case EquipSlot.Body:
|
||||
|
|
@ -108,147 +200,148 @@ internal class ObjectIdentification : IObjectIdentifier
|
|||
case EquipSlot.FullBody:
|
||||
case EquipSlot.HeadBody:
|
||||
case EquipSlot.LegsFeet:
|
||||
Add( equipment, EquipmentKey( item ), item );
|
||||
case EquipSlot.ChestHands:
|
||||
Add(storage, EquipmentKey(item), item);
|
||||
break;
|
||||
default: continue;
|
||||
}
|
||||
}
|
||||
|
||||
_actions = new Dictionary< string, HashSet< Action > >();
|
||||
foreach( var action in dataManager.GetExcelSheet< Action >( clientLanguage )!
|
||||
.Where( a => a.Name.ToString().Any() ) )
|
||||
return storage.Select(kvp => (kvp.Key, (IReadOnlyList<Item>)kvp.Value.ToArray())).ToList();
|
||||
}
|
||||
|
||||
private IReadOnlyDictionary<string, IReadOnlyList<Action>> CreateActionList()
|
||||
{
|
||||
var sheet = _dataManager.GetExcelSheet<Action>(_language)!;
|
||||
var storage = new Dictionary<string, HashSet<Action>>((int)sheet.RowCount);
|
||||
|
||||
void AddAction(string? key, Action action)
|
||||
{
|
||||
var startKey = action.AnimationStart?.Value?.Name?.Value?.Key.ToString() ?? string.Empty;
|
||||
var endKey = action.AnimationEnd?.Value?.Key.ToString() ?? string.Empty;
|
||||
var hitKey = action.ActionTimelineHit?.Value?.Key.ToString() ?? string.Empty;
|
||||
AddAction( startKey, action );
|
||||
AddAction( endKey, action );
|
||||
AddAction( hitKey, action );
|
||||
if (key.IsNullOrEmpty())
|
||||
return;
|
||||
|
||||
key = key.ToLowerInvariant();
|
||||
if (storage.TryGetValue(key, out var actions))
|
||||
actions.Add(action);
|
||||
else
|
||||
storage[key] = new HashSet<Action> { action };
|
||||
}
|
||||
|
||||
_weapons = weapons.Select( kvp => ( kvp.Key, kvp.Value ) ).ToList();
|
||||
_equipment = equipment.Select( kvp => ( kvp.Key, kvp.Value ) ).ToList();
|
||||
foreach (var action in sheet.Where(a => !a.Name.RawData.IsEmpty))
|
||||
{
|
||||
var startKey = action.AnimationStart?.Value?.Name?.Value?.Key.ToDalamudString().ToString();
|
||||
var endKey = action.AnimationEnd?.Value?.Key.ToDalamudString().ToString();
|
||||
var hitKey = action.ActionTimelineHit?.Value?.Key.ToDalamudString().ToString();
|
||||
AddAction(startKey, action);
|
||||
AddAction(endKey, action);
|
||||
AddAction(hitKey, action);
|
||||
}
|
||||
|
||||
return storage.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<Action>)kvp.Value.ToArray());
|
||||
}
|
||||
|
||||
private class Comparer : IComparer< (ulong, HashSet< Item >) >
|
||||
private class Comparer : IComparer<(ulong, IReadOnlyList<Item>)>
|
||||
{
|
||||
public int Compare( (ulong, HashSet< Item >) x, (ulong, HashSet< Item >) y )
|
||||
=> x.Item1.CompareTo( y.Item1 );
|
||||
public int Compare((ulong, IReadOnlyList<Item>) x, (ulong, IReadOnlyList<Item>) y)
|
||||
=> x.Item1.CompareTo(y.Item1);
|
||||
}
|
||||
|
||||
private static (int, int) FindIndexRange( List< (ulong, HashSet< Item >) > list, ulong key, ulong mask )
|
||||
private static (int, int) FindIndexRange(List<(ulong, IReadOnlyList<Item>)> list, ulong key, ulong mask)
|
||||
{
|
||||
var maskedKey = key & mask;
|
||||
var idx = list.BinarySearch( 0, list.Count, ( key, null! ), new Comparer() );
|
||||
if( idx < 0 )
|
||||
var idx = list.BinarySearch(0, list.Count, (key, null!), new Comparer());
|
||||
if (idx < 0)
|
||||
{
|
||||
if( ~idx == list.Count || maskedKey != ( list[ ~idx ].Item1 & mask ) )
|
||||
{
|
||||
return ( -1, -1 );
|
||||
}
|
||||
if (~idx == list.Count || maskedKey != (list[~idx].Item1 & mask))
|
||||
return (-1, -1);
|
||||
|
||||
idx = ~idx;
|
||||
}
|
||||
|
||||
var endIdx = idx + 1;
|
||||
while( endIdx < list.Count && maskedKey == ( list[ endIdx ].Item1 & mask ) )
|
||||
{
|
||||
while (endIdx < list.Count && maskedKey == (list[endIdx].Item1 & mask))
|
||||
++endIdx;
|
||||
}
|
||||
|
||||
return ( idx, endIdx );
|
||||
return (idx, endIdx);
|
||||
}
|
||||
|
||||
private void FindEquipment( IDictionary< string, object? > set, GameObjectInfo info )
|
||||
private void FindEquipment(IDictionary<string, object?> set, GameObjectInfo info)
|
||||
{
|
||||
var key = ( ulong )info.PrimaryId << 32;
|
||||
var key = (ulong)info.PrimaryId << 32;
|
||||
var mask = 0xFFFF00000000ul;
|
||||
if( info.EquipSlot != EquipSlot.Unknown )
|
||||
if (info.EquipSlot != EquipSlot.Unknown)
|
||||
{
|
||||
key |= ( ulong )info.EquipSlot.ToSlot() << 16;
|
||||
key |= (ulong)info.EquipSlot.ToSlot() << 16;
|
||||
mask |= 0xFFFF0000;
|
||||
}
|
||||
|
||||
if( info.Variant != 0 )
|
||||
if (info.Variant != 0)
|
||||
{
|
||||
key |= info.Variant;
|
||||
mask |= 0xFFFF;
|
||||
}
|
||||
|
||||
var (start, end) = FindIndexRange( _equipment, key, mask );
|
||||
if( start == -1 )
|
||||
{
|
||||
var (start, end) = FindIndexRange((List<(ulong, IReadOnlyList<Item>)>)_equipment, key, mask);
|
||||
if (start == -1)
|
||||
return;
|
||||
}
|
||||
|
||||
for( ; start < end; ++start )
|
||||
for (; start < end; ++start)
|
||||
{
|
||||
foreach( var item in _equipment[ start ].Item2 )
|
||||
{
|
||||
set[ item.Name.ToString() ] = item;
|
||||
}
|
||||
foreach (var item in _equipment[start].Item2)
|
||||
set[item.Name.ToString()] = item;
|
||||
}
|
||||
}
|
||||
|
||||
private void FindWeapon( IDictionary< string, object? > set, GameObjectInfo info )
|
||||
private void FindWeapon(IDictionary<string, object?> set, GameObjectInfo info)
|
||||
{
|
||||
var key = ( ulong )info.PrimaryId << 32;
|
||||
var key = (ulong)info.PrimaryId << 32;
|
||||
var mask = 0xFFFF00000000ul;
|
||||
if( info.SecondaryId != 0 )
|
||||
if (info.SecondaryId != 0)
|
||||
{
|
||||
key |= ( ulong )info.SecondaryId << 16;
|
||||
key |= (ulong)info.SecondaryId << 16;
|
||||
mask |= 0xFFFF0000;
|
||||
}
|
||||
|
||||
if( info.Variant != 0 )
|
||||
if (info.Variant != 0)
|
||||
{
|
||||
key |= info.Variant;
|
||||
mask |= 0xFFFF;
|
||||
}
|
||||
|
||||
var (start, end) = FindIndexRange( _weapons, key, mask );
|
||||
if( start == -1 )
|
||||
{
|
||||
var (start, end) = FindIndexRange((List<(ulong, IReadOnlyList<Item>)>)_weapons, key, mask);
|
||||
if (start == -1)
|
||||
return;
|
||||
}
|
||||
|
||||
for( ; start < end; ++start )
|
||||
for (; start < end; ++start)
|
||||
{
|
||||
foreach( var item in _weapons[ start ].Item2 )
|
||||
{
|
||||
set[ item.Name.ToString() ] = item;
|
||||
}
|
||||
foreach (var item in _weapons[start].Item2)
|
||||
set[item.Name.ToString()] = item;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddCounterString( IDictionary< string, object? > set, string data )
|
||||
private static void AddCounterString(IDictionary<string, object?> set, string data)
|
||||
{
|
||||
if( set.TryGetValue( data, out var obj ) && obj is int counter )
|
||||
{
|
||||
set[ data ] = counter + 1;
|
||||
}
|
||||
if (set.TryGetValue(data, out var obj) && obj is int counter)
|
||||
set[data] = counter + 1;
|
||||
else
|
||||
{
|
||||
set[ data ] = 1;
|
||||
}
|
||||
set[data] = 1;
|
||||
}
|
||||
|
||||
private void IdentifyParsed( IDictionary< string, object? > set, GameObjectInfo info )
|
||||
private void IdentifyParsed(IDictionary<string, object?> set, GameObjectInfo info)
|
||||
{
|
||||
switch( info.ObjectType )
|
||||
switch (info.ObjectType)
|
||||
{
|
||||
case ObjectType.Unknown:
|
||||
switch( info.FileType )
|
||||
switch (info.FileType)
|
||||
{
|
||||
case FileType.Sound:
|
||||
AddCounterString( set, FileType.Sound.ToString() );
|
||||
AddCounterString(set, FileType.Sound.ToString());
|
||||
break;
|
||||
case FileType.Animation:
|
||||
case FileType.Pap:
|
||||
AddCounterString( set, FileType.Animation.ToString() );
|
||||
AddCounterString(set, FileType.Animation.ToString());
|
||||
break;
|
||||
case FileType.Shader:
|
||||
AddCounterString( set, FileType.Shader.ToString() );
|
||||
AddCounterString(set, FileType.Shader.ToString());
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -260,50 +353,50 @@ internal class ObjectIdentification : IObjectIdentifier
|
|||
case ObjectType.World:
|
||||
case ObjectType.Housing:
|
||||
case ObjectType.Font:
|
||||
AddCounterString( set, info.ObjectType.ToString() );
|
||||
AddCounterString(set, info.ObjectType.ToString());
|
||||
break;
|
||||
case ObjectType.DemiHuman:
|
||||
set[ $"Demi Human: {info.PrimaryId}" ] = null;
|
||||
set[$"Demi Human: {info.PrimaryId}"] = null;
|
||||
break;
|
||||
case ObjectType.Monster:
|
||||
set[ $"Monster: {info.PrimaryId}" ] = null;
|
||||
set[$"Monster: {info.PrimaryId}"] = null;
|
||||
break;
|
||||
case ObjectType.Icon:
|
||||
set[ $"Icon: {info.IconId}" ] = null;
|
||||
set[$"Icon: {info.IconId}"] = null;
|
||||
break;
|
||||
case ObjectType.Accessory:
|
||||
case ObjectType.Equipment:
|
||||
FindEquipment( set, info );
|
||||
FindEquipment(set, info);
|
||||
break;
|
||||
case ObjectType.Weapon:
|
||||
FindWeapon( set, info );
|
||||
FindWeapon(set, info);
|
||||
break;
|
||||
case ObjectType.Character:
|
||||
var (gender, race) = info.GenderRace.Split();
|
||||
var raceString = race != ModelRace.Unknown ? race.ToName() + " " : "";
|
||||
var genderString = gender != Gender.Unknown ? gender.ToName() + " " : "Player ";
|
||||
switch( info.CustomizationType )
|
||||
var raceString = race != ModelRace.Unknown ? race.ToName() + " " : "";
|
||||
var genderString = gender != Gender.Unknown ? gender.ToName() + " " : "Player ";
|
||||
switch (info.CustomizationType)
|
||||
{
|
||||
case CustomizationType.Skin:
|
||||
set[ $"Customization: {raceString}{genderString}Skin Textures" ] = null;
|
||||
set[$"Customization: {raceString}{genderString}Skin Textures"] = null;
|
||||
break;
|
||||
case CustomizationType.DecalFace:
|
||||
set[ $"Customization: Face Decal {info.PrimaryId}" ] = null;
|
||||
set[$"Customization: Face Decal {info.PrimaryId}"] = null;
|
||||
break;
|
||||
case CustomizationType.Iris when race == ModelRace.Unknown:
|
||||
set[ $"Customization: All Eyes (Catchlight)" ] = null;
|
||||
set[$"Customization: All Eyes (Catchlight)"] = null;
|
||||
break;
|
||||
case CustomizationType.DecalEquip:
|
||||
set[ $"Equipment Decal {info.PrimaryId}" ] = null;
|
||||
set[$"Equipment Decal {info.PrimaryId}"] = null;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
var customizationString = race == ModelRace.Unknown
|
||||
|| info.BodySlot == BodySlot.Unknown
|
||||
|| info.CustomizationType == CustomizationType.Unknown
|
||||
|| info.BodySlot == BodySlot.Unknown
|
||||
|| info.CustomizationType == CustomizationType.Unknown
|
||||
? "Customization: Unknown"
|
||||
: $"Customization: {race} {gender} {info.BodySlot} ({info.CustomizationType}) {info.PrimaryId}";
|
||||
set[ customizationString ] = null;
|
||||
set[customizationString] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -314,58 +407,13 @@ internal class ObjectIdentification : IObjectIdentifier
|
|||
}
|
||||
}
|
||||
|
||||
private void IdentifyVfx( IDictionary< string, object? > set, GamePath path )
|
||||
private void IdentifyVfx(IDictionary<string, object?> set, string path)
|
||||
{
|
||||
var key = GameData.GamePathParser.VfxToKey( path );
|
||||
if( key.Length == 0 || !_actions.TryGetValue( key, out var actions ) )
|
||||
{
|
||||
var key = GamePathParser.VfxToKey(path);
|
||||
if (key.Length == 0 || !_actions.TryGetValue(key, out var actions))
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( var action in actions )
|
||||
{
|
||||
set[ $"Action: {action.Name}" ] = action;
|
||||
}
|
||||
foreach (var action in actions)
|
||||
set[$"Action: {action.Name}"] = action;
|
||||
}
|
||||
|
||||
public void Identify( IDictionary< string, object? > set, GamePath path )
|
||||
{
|
||||
if( ( ( string )path ).EndsWith( ".pap" ) || ( ( string )path ).EndsWith( ".tmb" ) )
|
||||
{
|
||||
IdentifyVfx( set, path );
|
||||
}
|
||||
else
|
||||
{
|
||||
var info = GameData.GamePathParser.GetFileInfo( path );
|
||||
IdentifyParsed( set, info );
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary< string, object? > Identify( GamePath path )
|
||||
{
|
||||
Dictionary< string, object? > ret = new();
|
||||
Identify( ret, path );
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Item? Identify( SetId setId, WeaponType weaponType, ushort variant, EquipSlot slot )
|
||||
{
|
||||
switch( slot )
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
case EquipSlot.OffHand:
|
||||
{
|
||||
var (begin, _) = FindIndexRange( _weapons, ( ( ulong )setId << 32 ) | ( ( ulong )weaponType << 16 ) | variant,
|
||||
0xFFFFFFFFFFFF );
|
||||
return begin >= 0 ? _weapons[ begin ].Item2.FirstOrDefault() : null;
|
||||
}
|
||||
default:
|
||||
{
|
||||
var (begin, _) = FindIndexRange( _equipment,
|
||||
( ( ulong )setId << 32 ) | ( ( ulong )slot.ToSlot() << 16 ) | variant,
|
||||
0xFFFFFFFFFFFF );
|
||||
return begin >= 0 ? _equipment[ begin ].Item2.FirstOrDefault() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.String.Functions;
|
||||
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
|
|
|||
|
|
@ -1,106 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using JsonSerializer = Newtonsoft.Json.JsonSerializer;
|
||||
|
||||
namespace Penumbra.GameData.Util;
|
||||
|
||||
public readonly struct GamePath : IComparable
|
||||
{
|
||||
public const int MaxGamePathLength = 256;
|
||||
|
||||
private readonly string _path;
|
||||
|
||||
private GamePath( string path, bool _ )
|
||||
=> _path = path;
|
||||
|
||||
public GamePath( string? path )
|
||||
{
|
||||
if( path is { Length: < MaxGamePathLength } )
|
||||
{
|
||||
_path = Lower( Trim( ReplaceSlash( path ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
_path = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public GamePath( FileInfo file, DirectoryInfo baseDir )
|
||||
=> _path = CheckPre( file, baseDir ) ? Lower( Trim( ReplaceSlash( Substring( file, baseDir ) ) ) ) : "";
|
||||
|
||||
private static bool CheckPre( FileInfo file, DirectoryInfo baseDir )
|
||||
=> file.FullName.StartsWith( baseDir.FullName ) && file.FullName.Length < MaxGamePathLength;
|
||||
|
||||
private static string Substring( FileInfo file, DirectoryInfo baseDir )
|
||||
=> file.FullName.Substring( baseDir.FullName.Length );
|
||||
|
||||
private static string ReplaceSlash( string path )
|
||||
=> path.Replace( '\\', '/' );
|
||||
|
||||
private static string Trim( string path )
|
||||
=> path.TrimStart( '/' );
|
||||
|
||||
private static string Lower( string path )
|
||||
=> path.ToLowerInvariant();
|
||||
|
||||
public static GamePath GenerateUnchecked( string path )
|
||||
=> new(path, true);
|
||||
|
||||
public static GamePath GenerateUncheckedLower( string path )
|
||||
=> new(Lower( path ), true);
|
||||
|
||||
public static implicit operator string( GamePath gamePath )
|
||||
=> gamePath._path;
|
||||
|
||||
public static explicit operator GamePath( string gamePath )
|
||||
=> new(gamePath);
|
||||
|
||||
public bool Empty
|
||||
=> _path.Length == 0;
|
||||
|
||||
public string Filename()
|
||||
{
|
||||
var idx = _path.LastIndexOf( "/", StringComparison.Ordinal );
|
||||
return idx == -1 ? _path : idx == _path.Length - 1 ? "" : _path[ ( idx + 1 ).. ];
|
||||
}
|
||||
|
||||
public int CompareTo( object? rhs )
|
||||
{
|
||||
return rhs switch
|
||||
{
|
||||
string path => string.Compare( _path, path, StringComparison.Ordinal ),
|
||||
GamePath path => string.Compare( _path, path._path, StringComparison.Ordinal ),
|
||||
_ => -1,
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> _path;
|
||||
}
|
||||
|
||||
public class GamePathConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert( Type objectType )
|
||||
=> objectType == typeof( GamePath );
|
||||
|
||||
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
|
||||
{
|
||||
var token = JToken.Load( reader );
|
||||
return token.ToObject< GamePath >();
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
=> true;
|
||||
|
||||
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
|
||||
{
|
||||
if( value != null )
|
||||
{
|
||||
var v = ( GamePath )value;
|
||||
serializer.Serialize( writer, v.ToString() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7,7 +7,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
|
@ -474,10 +473,10 @@ public partial class ModCollection
|
|||
_changedItems.Clear();
|
||||
// Skip IMCs because they would result in far too many false-positive items,
|
||||
// since they are per set instead of per item-slot/item/variant.
|
||||
var identifier = GameData.GameData.GetIdentifier();
|
||||
var identifier = Penumbra.Identifier;
|
||||
foreach( var (resolved, modPath) in ResolvedFiles.Where( file => !file.Key.Path.EndsWith( "imc"u8 ) ) )
|
||||
{
|
||||
foreach( var (name, obj) in identifier.Identify( new GamePath( resolved.ToString() ) ) )
|
||||
foreach( var (name, obj) in identifier.Identify( resolved.ToString() ) )
|
||||
{
|
||||
if( !_changedItems.TryGetValue( name, out var data ) )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Util;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Penumbra.Import;
|
||||
|
|
@ -54,10 +53,6 @@ public class MetaFileInfo
|
|||
}
|
||||
|
||||
public MetaFileInfo( string fileName )
|
||||
: this( new GamePath( fileName ) )
|
||||
{ }
|
||||
|
||||
public MetaFileInfo( GamePath fileName )
|
||||
{
|
||||
// Set the primary type from the gamePath start.
|
||||
PrimaryType = GameData.GameData.GetGamePathParser().PathToObjectType( fileName );
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Interop.Structs;
|
||||
using System.Collections.Generic;
|
||||
using Penumbra.String.Functions;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.String.Functions;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.String.Functions;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.String.Functions;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using Dalamud.Memory;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.String.Functions;
|
||||
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.String.Functions;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Penumbra.GameData.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
|
|
@ -11,11 +10,10 @@ public sealed partial class Mod
|
|||
|
||||
private void ComputeChangedItems()
|
||||
{
|
||||
var identifier = GameData.GameData.GetIdentifier();
|
||||
ChangedItems.Clear();
|
||||
foreach( var gamePath in AllRedirects )
|
||||
{
|
||||
identifier.Identify( ChangedItems, new GamePath(gamePath.ToString()) );
|
||||
Penumbra.Identifier.Identify( ChangedItems, gamePath.ToString() );
|
||||
}
|
||||
|
||||
// TODO: manipulations
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ public class Penumbra : IDalamudPlugin
|
|||
public static ResourceLoader ResourceLoader { get; private set; } = null!;
|
||||
public static FrameworkManager Framework { get; private set; } = null!;
|
||||
public static ActorManager Actors { get; private set; } = null!;
|
||||
public static IObjectIdentifier Identifier { get; private set; } = null!;
|
||||
|
||||
public static readonly List< Exception > ImcExceptions = new();
|
||||
|
||||
|
|
@ -80,8 +81,8 @@ public class Penumbra : IDalamudPlugin
|
|||
try
|
||||
{
|
||||
Dalamud.Initialize( pluginInterface );
|
||||
Log = new Logger();
|
||||
GameData.GameData.GetIdentifier( Dalamud.GameData );
|
||||
Log = new Logger();
|
||||
Identifier = GameData.GameData.GetIdentifier( Dalamud.PluginInterface, Dalamud.GameData );
|
||||
DevPenumbraExists = CheckDevPluginPenumbra();
|
||||
IsNotInstalledPenumbra = CheckIsNotInstalled();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue