mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Extend the item identification a bit to count unidentifiable items and handle icons, demihumans and monsters.
This commit is contained in:
parent
1ad7787e4c
commit
06deddcd8a
7 changed files with 714 additions and 672 deletions
|
|
@ -7,34 +7,34 @@ using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
|
|
||||||
namespace Penumbra.GameData
|
namespace Penumbra.GameData;
|
||||||
|
|
||||||
|
internal class GamePathParser : IGamePathParser
|
||||||
{
|
{
|
||||||
internal class GamePathParser : IGamePathParser
|
private const string CharacterFolder = "chara";
|
||||||
{
|
private const string EquipmentFolder = "equipment";
|
||||||
private const string CharacterFolder = "chara";
|
private const string PlayerFolder = "human";
|
||||||
private const string EquipmentFolder = "equipment";
|
private const string WeaponFolder = "weapon";
|
||||||
private const string PlayerFolder = "human";
|
private const string AccessoryFolder = "accessory";
|
||||||
private const string WeaponFolder = "weapon";
|
private const string DemiHumanFolder = "demihuman";
|
||||||
private const string AccessoryFolder = "accessory";
|
private const string MonsterFolder = "monster";
|
||||||
private const string DemiHumanFolder = "demihuman";
|
private const string CommonFolder = "common";
|
||||||
private const string MonsterFolder = "monster";
|
private const string UiFolder = "ui";
|
||||||
private const string CommonFolder = "common";
|
private const string IconFolder = "icon";
|
||||||
private const string UiFolder = "ui";
|
private const string LoadingFolder = "loadingimage";
|
||||||
private const string IconFolder = "icon";
|
private const string MapFolder = "map";
|
||||||
private const string LoadingFolder = "loadingimage";
|
private const string InterfaceFolder = "uld";
|
||||||
private const string MapFolder = "map";
|
private const string FontFolder = "font";
|
||||||
private const string InterfaceFolder = "uld";
|
private const string HousingFolder = "hou";
|
||||||
private const string FontFolder = "font";
|
private const string VfxFolder = "vfx";
|
||||||
private const string HousingFolder = "hou";
|
private const string WorldFolder1 = "bgcommon";
|
||||||
private const string VfxFolder = "vfx";
|
private const string WorldFolder2 = "bg";
|
||||||
private const string WorldFolder1 = "bgcommon";
|
|
||||||
private const string WorldFolder2 = "bg";
|
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
private readonly Dictionary<FileType, Dictionary<ObjectType, Regex[]>> _regexes = new()
|
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.Font, new Dictionary< ObjectType, Regex[] >(){ { ObjectType.Font, new Regex[]{ new(@"common/font/(?'fontname'.*)_(?'id'\d\d)(_lobby)?\.fdt") } } } }
|
||||||
, { FileType.Texture, new Dictionary< ObjectType, Regex[] >()
|
, { FileType.Texture, new Dictionary< ObjectType, Regex[] >()
|
||||||
{ { ObjectType.Icon, new Regex[]{ new(@"ui/icon/(?'group'\d*)(/(?'lang'[a-z]{2}))?(/(?'hq'hq))?/(?'id'\d*)\.tex") } }
|
{ { 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.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.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.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") } }
|
||||||
|
|
@ -58,7 +58,7 @@ namespace Penumbra.GameData
|
||||||
, { 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.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.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.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") } } } }
|
, { 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[] >()
|
, { 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.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.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/b\k'id'\.imc") } }
|
||||||
|
|
@ -66,267 +66,267 @@ namespace Penumbra.GameData
|
||||||
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/e\k'equip'\.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") } } } },
|
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/a\k'id'\.imc") } } } },
|
||||||
};
|
};
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
public ObjectType PathToObjectType( GamePath path )
|
public ObjectType PathToObjectType( GamePath path )
|
||||||
|
{
|
||||||
|
if( path.Empty )
|
||||||
{
|
{
|
||||||
if( path.Empty )
|
return ObjectType.Unknown;
|
||||||
{
|
|
||||||
return ObjectType.Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
string p = path;
|
|
||||||
var folders = p.Split( '/' );
|
|
||||||
if( folders.Length < 2 )
|
|
||||||
{
|
|
||||||
return ObjectType.Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
return folders[ 0 ] switch
|
|
||||||
{
|
|
||||||
CharacterFolder => folders[ 1 ] switch
|
|
||||||
{
|
|
||||||
EquipmentFolder => ObjectType.Equipment,
|
|
||||||
AccessoryFolder => ObjectType.Accessory,
|
|
||||||
WeaponFolder => ObjectType.Weapon,
|
|
||||||
PlayerFolder => ObjectType.Character,
|
|
||||||
DemiHumanFolder => ObjectType.DemiHuman,
|
|
||||||
MonsterFolder => ObjectType.Monster,
|
|
||||||
CommonFolder => ObjectType.Character,
|
|
||||||
_ => ObjectType.Unknown,
|
|
||||||
},
|
|
||||||
UiFolder => folders[ 1 ] switch
|
|
||||||
{
|
|
||||||
IconFolder => ObjectType.Icon,
|
|
||||||
LoadingFolder => ObjectType.LoadingScreen,
|
|
||||||
MapFolder => ObjectType.Map,
|
|
||||||
InterfaceFolder => ObjectType.Interface,
|
|
||||||
_ => ObjectType.Unknown,
|
|
||||||
},
|
|
||||||
CommonFolder => folders[ 1 ] switch
|
|
||||||
{
|
|
||||||
FontFolder => ObjectType.Font,
|
|
||||||
_ => ObjectType.Unknown,
|
|
||||||
},
|
|
||||||
HousingFolder => ObjectType.Housing,
|
|
||||||
WorldFolder1 => folders[ 1 ] switch
|
|
||||||
{
|
|
||||||
HousingFolder => ObjectType.Housing,
|
|
||||||
_ => ObjectType.World,
|
|
||||||
},
|
|
||||||
WorldFolder2 => ObjectType.World,
|
|
||||||
VfxFolder => ObjectType.Vfx,
|
|
||||||
_ => ObjectType.Unknown,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private (FileType, ObjectType, Match?) ParseGamePath( GamePath path )
|
string p = path;
|
||||||
|
var folders = p.Split( '/' );
|
||||||
|
if( folders.Length < 2 )
|
||||||
{
|
{
|
||||||
if( !Names.ExtensionToFileType.TryGetValue( Extension( path ), out var fileType ) )
|
return ObjectType.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
return folders[ 0 ] switch
|
||||||
|
{
|
||||||
|
CharacterFolder => folders[ 1 ] switch
|
||||||
{
|
{
|
||||||
fileType = FileType.Unknown;
|
EquipmentFolder => ObjectType.Equipment,
|
||||||
}
|
AccessoryFolder => ObjectType.Accessory,
|
||||||
|
WeaponFolder => ObjectType.Weapon,
|
||||||
var objectType = PathToObjectType( path );
|
PlayerFolder => ObjectType.Character,
|
||||||
|
DemiHumanFolder => ObjectType.DemiHuman,
|
||||||
if( !_regexes.TryGetValue( fileType, out var objectDict ) )
|
MonsterFolder => ObjectType.Monster,
|
||||||
|
CommonFolder => ObjectType.Character,
|
||||||
|
_ => ObjectType.Unknown,
|
||||||
|
},
|
||||||
|
UiFolder => folders[ 1 ] switch
|
||||||
{
|
{
|
||||||
return ( fileType, objectType, null );
|
IconFolder => ObjectType.Icon,
|
||||||
}
|
LoadingFolder => ObjectType.LoadingScreen,
|
||||||
|
MapFolder => ObjectType.Map,
|
||||||
if( !objectDict.TryGetValue( objectType, out var regexes ) )
|
InterfaceFolder => ObjectType.Interface,
|
||||||
|
_ => ObjectType.Unknown,
|
||||||
|
},
|
||||||
|
CommonFolder => folders[ 1 ] switch
|
||||||
{
|
{
|
||||||
return ( fileType, objectType, null );
|
FontFolder => ObjectType.Font,
|
||||||
}
|
_ => ObjectType.Unknown,
|
||||||
|
},
|
||||||
foreach( var regex in regexes )
|
HousingFolder => ObjectType.Housing,
|
||||||
|
WorldFolder1 => folders[ 1 ] switch
|
||||||
{
|
{
|
||||||
var match = regex.Match( path );
|
HousingFolder => ObjectType.Housing,
|
||||||
if( match.Success )
|
_ => ObjectType.World,
|
||||||
{
|
},
|
||||||
return ( fileType, objectType, match );
|
WorldFolder2 => ObjectType.World,
|
||||||
}
|
VfxFolder => ObjectType.Vfx,
|
||||||
}
|
_ => ObjectType.Unknown,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private (FileType, ObjectType, Match?) ParseGamePath( GamePath path )
|
||||||
|
{
|
||||||
|
if( !Names.ExtensionToFileType.TryGetValue( Extension( path ), out var fileType ) )
|
||||||
|
{
|
||||||
|
fileType = FileType.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
var objectType = PathToObjectType( path );
|
||||||
|
|
||||||
|
if( !_regexes.TryGetValue( fileType, out var objectDict ) )
|
||||||
|
{
|
||||||
return ( fileType, objectType, null );
|
return ( fileType, objectType, null );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Extension( string filename )
|
if( !objectDict.TryGetValue( objectType, out var regexes ) )
|
||||||
{
|
{
|
||||||
var extIdx = filename.LastIndexOf( '.' );
|
return ( fileType, objectType, null );
|
||||||
return extIdx < 0 ? "" : filename.Substring( extIdx );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GameObjectInfo HandleEquipment( FileType fileType, GroupCollection groups )
|
foreach( var regex in regexes )
|
||||||
{
|
{
|
||||||
var setId = ushort.Parse( groups[ "id" ].Value );
|
var match = regex.Match( path );
|
||||||
if( fileType == FileType.Imc )
|
if( match.Success )
|
||||||
{
|
{
|
||||||
return GameObjectInfo.Equipment( fileType, setId );
|
return ( fileType, objectType, match );
|
||||||
}
|
}
|
||||||
|
|
||||||
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 HandleWeapon( FileType fileType, GroupCollection groups )
|
return ( fileType, objectType, null );
|
||||||
{
|
}
|
||||||
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 variant = byte.Parse( groups[ "variant" ].Value );
|
private static string Extension( string filename )
|
||||||
return GameObjectInfo.Weapon( fileType, setId, weaponId, variant );
|
{
|
||||||
|
var extIdx = filename.LastIndexOf( '.' );
|
||||||
|
return extIdx < 0 ? "" : filename.Substring( extIdx );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameObjectInfo HandleEquipment( FileType fileType, GroupCollection groups )
|
||||||
|
{
|
||||||
|
var setId = ushort.Parse( groups[ "id" ].Value );
|
||||||
|
if( fileType == FileType.Imc )
|
||||||
|
{
|
||||||
|
return GameObjectInfo.Equipment( fileType, setId );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GameObjectInfo HandleMonster( FileType fileType, GroupCollection groups )
|
var gr = Names.GenderRaceFromCode( groups[ "race" ].Value );
|
||||||
|
var slot = Names.SuffixToEquipSlot[ groups[ "slot" ].Value ];
|
||||||
|
if( fileType == FileType.Model )
|
||||||
{
|
{
|
||||||
var monsterId = ushort.Parse( groups[ "monster" ].Value );
|
return GameObjectInfo.Equipment( fileType, setId, gr, slot );
|
||||||
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.Monster( fileType, monsterId, bodyId, variant );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GameObjectInfo HandleDemiHuman( FileType fileType, GroupCollection groups )
|
var variant = byte.Parse( groups[ "variant" ].Value );
|
||||||
|
return GameObjectInfo.Equipment( fileType, setId, gr, slot, variant );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameObjectInfo HandleWeapon( 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 )
|
||||||
{
|
{
|
||||||
var demiHumanId = ushort.Parse( groups[ "id" ].Value );
|
return GameObjectInfo.Weapon( fileType, setId, weaponId );
|
||||||
var equipId = ushort.Parse( groups[ "equip" ].Value );
|
|
||||||
if( fileType == FileType.Imc )
|
|
||||||
{
|
|
||||||
return GameObjectInfo.DemiHuman( fileType, demiHumanId, equipId );
|
|
||||||
}
|
|
||||||
|
|
||||||
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 )
|
var variant = byte.Parse( groups[ "variant" ].Value );
|
||||||
|
return GameObjectInfo.Weapon( fileType, setId, weaponId, variant );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameObjectInfo HandleMonster( 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 )
|
||||||
{
|
{
|
||||||
if( groups[ "skin" ].Success )
|
return GameObjectInfo.Monster( fileType, monsterId, bodyId );
|
||||||
{
|
|
||||||
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 ]
|
|
||||||
: CustomizationType.Skin;
|
|
||||||
if( fileType == FileType.Material )
|
|
||||||
{
|
|
||||||
var variant = byte.Parse( groups[ "variant" ].Value );
|
|
||||||
return GameObjectInfo.Customization( fileType, type, id, gr, bodySlot, variant );
|
|
||||||
}
|
|
||||||
|
|
||||||
return GameObjectInfo.Customization( fileType, type, id, gr, bodySlot );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GameObjectInfo HandleIcon( FileType fileType, GroupCollection groups )
|
var variant = byte.Parse( groups[ "variant" ].Value );
|
||||||
{
|
return GameObjectInfo.Monster( fileType, monsterId, bodyId, variant );
|
||||||
var hq = groups[ "hq" ].Success;
|
}
|
||||||
var id = uint.Parse( groups[ "id" ].Value );
|
|
||||||
if( !groups[ "lang" ].Success )
|
|
||||||
{
|
|
||||||
return GameObjectInfo.Icon( fileType, id, hq );
|
|
||||||
}
|
|
||||||
|
|
||||||
var language = groups[ "lang" ].Value switch
|
private static GameObjectInfo HandleDemiHuman( FileType fileType, GroupCollection groups )
|
||||||
{
|
{
|
||||||
"en" => Dalamud.ClientLanguage.English,
|
var demiHumanId = ushort.Parse( groups[ "id" ].Value );
|
||||||
"ja" => Dalamud.ClientLanguage.Japanese,
|
var equipId = ushort.Parse( groups[ "equip" ].Value );
|
||||||
"de" => Dalamud.ClientLanguage.German,
|
if( fileType == FileType.Imc )
|
||||||
"fr" => Dalamud.ClientLanguage.French,
|
{
|
||||||
_ => Dalamud.ClientLanguage.English,
|
return GameObjectInfo.DemiHuman( fileType, demiHumanId, equipId );
|
||||||
};
|
|
||||||
return GameObjectInfo.Icon( fileType, id, hq, language );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GameObjectInfo HandleMap( FileType fileType, GroupCollection groups )
|
var slot = Names.SuffixToEquipSlot[ groups[ "slot" ].Value ];
|
||||||
|
if( fileType == FileType.Model )
|
||||||
{
|
{
|
||||||
var map = Encoding.ASCII.GetBytes( groups[ "id" ].Value );
|
return GameObjectInfo.DemiHuman( fileType, demiHumanId, equipId, slot );
|
||||||
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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
return GameObjectInfo.Map( fileType, map[ 0 ], map[ 1 ], map[ 2 ], map[ 3 ], variant );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public GameObjectInfo GetFileInfo( GamePath path )
|
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[ "skin" ].Success )
|
||||||
{
|
{
|
||||||
var (fileType, objectType, match) = ParseGamePath( path );
|
return GameObjectInfo.Customization( fileType, CustomizationType.Skin );
|
||||||
if( match == null || !match.Success )
|
}
|
||||||
{
|
|
||||||
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
var id = ushort.Parse( groups[ "id" ].Value );
|
||||||
{
|
if( groups[ "location" ].Success )
|
||||||
var groups = match.Groups;
|
{
|
||||||
switch( objectType )
|
var tmpType = groups[ "location" ].Value == "face" ? CustomizationType.DecalFace
|
||||||
{
|
: groups[ "location" ].Value == "equip" ? CustomizationType.DecalEquip : CustomizationType.Unknown;
|
||||||
case ObjectType.Accessory: return HandleEquipment( fileType, groups );
|
return GameObjectInfo.Customization( fileType, tmpType, id );
|
||||||
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}" );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 )
|
||||||
|
{
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
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 language = groups[ "lang" ].Value switch
|
||||||
|
{
|
||||||
|
"en" => Dalamud.ClientLanguage.English,
|
||||||
|
"ja" => Dalamud.ClientLanguage.Japanese,
|
||||||
|
"de" => Dalamud.ClientLanguage.German,
|
||||||
|
"fr" => Dalamud.ClientLanguage.French,
|
||||||
|
_ => Dalamud.ClientLanguage.English,
|
||||||
|
};
|
||||||
|
return GameObjectInfo.Icon( fileType, id, hq, hr, language );
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameObjectInfo GetFileInfo( GamePath path )
|
||||||
|
{
|
||||||
|
var (fileType, objectType, match) = ParseGamePath( path );
|
||||||
|
if( match == null || !match.Success )
|
||||||
|
{
|
||||||
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
|
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Regex _vfxRegexTmb = new( @"chara/action/(?'key'[^\s]+?)\.tmb" );
|
try
|
||||||
private readonly Regex _vfxRegexPap = new( @"chara/human/c0101/animation/a0001/[^\s]+?/(?'key'[^\s]+?)\.pap" );
|
|
||||||
|
|
||||||
public string VfxToKey( GamePath path )
|
|
||||||
{
|
{
|
||||||
var match = _vfxRegexTmb.Match( path );
|
var groups = match.Groups;
|
||||||
if( match.Success )
|
switch( objectType )
|
||||||
{
|
{
|
||||||
return match.Groups[ "key" ].Value.ToLowerInvariant();
|
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 );
|
||||||
}
|
}
|
||||||
|
|
||||||
match = _vfxRegexPap.Match( path );
|
|
||||||
return match.Success ? match.Groups[ "key" ].Value.ToLowerInvariant() : string.Empty;
|
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,324 +1,355 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
using Dalamud;
|
using Dalamud;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
using Action = Lumina.Excel.GeneratedSheets.Action;
|
using Action = Lumina.Excel.GeneratedSheets.Action;
|
||||||
|
|
||||||
namespace Penumbra.GameData
|
namespace Penumbra.GameData;
|
||||||
|
|
||||||
|
internal class ObjectIdentification : IObjectIdentifier
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
|
||||||
|
private static bool Add( IDictionary< ulong, HashSet< Item > > dict, ulong key, Item item )
|
||||||
{
|
{
|
||||||
public static DataManager? DataManager = null!;
|
if( dict.TryGetValue( key, out var list ) )
|
||||||
private readonly List< (ulong, HashSet< Item >) > _weapons;
|
|
||||||
private readonly List< (ulong, HashSet< Item >) > _equipment;
|
|
||||||
private readonly Dictionary< string, HashSet< Action > > _actions;
|
|
||||||
|
|
||||||
private static bool Add( IDictionary< ulong, HashSet< Item > > dict, ulong key, Item item )
|
|
||||||
{
|
{
|
||||||
if( dict.TryGetValue( key, out var list ) )
|
return list.Add( item );
|
||||||
{
|
|
||||||
return list.Add( item );
|
|
||||||
}
|
|
||||||
|
|
||||||
dict[ key ] = new HashSet< Item > { item };
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong EquipmentKey( Item i )
|
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 )
|
||||||
{
|
{
|
||||||
var model = ( ulong )( ( Lumina.Data.Parsing.Quad )i.ModelMain ).A;
|
return;
|
||||||
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 )
|
key = key.ToLowerInvariant();
|
||||||
|
if( _actions.TryGetValue( key, out var actions ) )
|
||||||
{
|
{
|
||||||
var quad = offhand ? ( Lumina.Data.Parsing.Quad )i.ModelSub : ( Lumina.Data.Parsing.Quad )i.ModelMain;
|
actions.Add( action );
|
||||||
var model = ( ulong )quad.A;
|
|
||||||
var type = ( ulong )quad.B;
|
|
||||||
var variant = ( ulong )quad.C;
|
|
||||||
|
|
||||||
return ( model << 32 ) | ( type << 16 ) | variant;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private void AddAction( string key, Action action )
|
|
||||||
{
|
{
|
||||||
if( key.Length == 0 )
|
_actions[ key ] = new HashSet< Action > { action };
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
key = key.ToLowerInvariant();
|
|
||||||
if( _actions.TryGetValue( key, out var actions ) )
|
|
||||||
{
|
|
||||||
actions.Add( action );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_actions[ key ] = new HashSet< Action > { action };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ObjectIdentification( DataManager dataManager, ClientLanguage clientLanguage )
|
public ObjectIdentification( DataManager dataManager, ClientLanguage clientLanguage )
|
||||||
|
{
|
||||||
|
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 )
|
||||||
{
|
{
|
||||||
DataManager = dataManager;
|
switch( ( EquipSlot )item.EquipSlotCategory.Row )
|
||||||
var items = dataManager.GetExcelSheet< Item >( clientLanguage )!;
|
|
||||||
SortedList< ulong, HashSet< Item > > weapons = new();
|
|
||||||
SortedList< ulong, HashSet< Item > > equipment = new();
|
|
||||||
foreach( var item in items )
|
|
||||||
{
|
|
||||||
switch( ( EquipSlot )item.EquipSlotCategory.Row )
|
|
||||||
{
|
|
||||||
case EquipSlot.MainHand:
|
|
||||||
case EquipSlot.OffHand:
|
|
||||||
case EquipSlot.BothHand:
|
|
||||||
if( item.ModelMain != 0 )
|
|
||||||
{
|
|
||||||
Add( weapons, WeaponKey( item, false ), item );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( item.ModelSub != 0 )
|
|
||||||
{
|
|
||||||
Add( weapons, WeaponKey( item, true ), item );
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
// 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:
|
|
||||||
case EquipSlot.Hands:
|
|
||||||
case EquipSlot.Legs:
|
|
||||||
case EquipSlot.Feet:
|
|
||||||
case EquipSlot.BodyHands:
|
|
||||||
case EquipSlot.BodyHandsLegsFeet:
|
|
||||||
case EquipSlot.BodyLegsFeet:
|
|
||||||
case EquipSlot.FullBody:
|
|
||||||
case EquipSlot.HeadBody:
|
|
||||||
case EquipSlot.LegsFeet:
|
|
||||||
Add( equipment, 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() ) )
|
|
||||||
{
|
|
||||||
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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
_weapons = weapons.Select( kvp => ( kvp.Key, kvp.Value ) ).ToList();
|
|
||||||
_equipment = equipment.Select( kvp => ( kvp.Key, kvp.Value ) ).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Comparer : IComparer< (ulong, HashSet< Item >) >
|
|
||||||
{
|
|
||||||
public int Compare( (ulong, HashSet< Item >) x, (ulong, HashSet< Item >) y )
|
|
||||||
=> x.Item1.CompareTo( y.Item1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (int, int) FindIndexRange( List< (ulong, HashSet< Item >) > list, ulong key, ulong mask )
|
|
||||||
{
|
|
||||||
var maskedKey = key & mask;
|
|
||||||
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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
idx = ~idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
var endIdx = idx + 1;
|
|
||||||
while( endIdx < list.Count && maskedKey == ( list[ endIdx ].Item1 & mask ) )
|
|
||||||
{
|
|
||||||
++endIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ( idx, endIdx );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FindEquipment( IDictionary< string, object? > set, GameObjectInfo info )
|
|
||||||
{
|
|
||||||
var key = ( ulong )info.PrimaryId << 32;
|
|
||||||
var mask = 0xFFFF00000000ul;
|
|
||||||
if( info.EquipSlot != EquipSlot.Unknown )
|
|
||||||
{
|
|
||||||
key |= ( ulong )info.EquipSlot.ToSlot() << 16;
|
|
||||||
mask |= 0xFFFF0000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( info.Variant != 0 )
|
|
||||||
{
|
|
||||||
key |= info.Variant;
|
|
||||||
mask |= 0xFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
var (start, end) = FindIndexRange( _equipment, key, mask );
|
|
||||||
if( start == -1 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for( ; start < end; ++start )
|
|
||||||
{
|
|
||||||
foreach( var item in _equipment[ start ].Item2 )
|
|
||||||
{
|
|
||||||
set[ item.Name.ToString() ] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FindWeapon( IDictionary< string, object? > set, GameObjectInfo info )
|
|
||||||
{
|
|
||||||
var key = ( ulong )info.PrimaryId << 32;
|
|
||||||
var mask = 0xFFFF00000000ul;
|
|
||||||
if( info.SecondaryId != 0 )
|
|
||||||
{
|
|
||||||
key |= ( ulong )info.SecondaryId << 16;
|
|
||||||
mask |= 0xFFFF0000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( info.Variant != 0 )
|
|
||||||
{
|
|
||||||
key |= info.Variant;
|
|
||||||
mask |= 0xFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
var (start, end) = FindIndexRange( _weapons, key, mask );
|
|
||||||
if( start == -1 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for( ; start < end; ++start )
|
|
||||||
{
|
|
||||||
foreach( var item in _weapons[ start ].Item2 )
|
|
||||||
{
|
|
||||||
set[ item.Name.ToString() ] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void IdentifyParsed( IDictionary< string, object? > set, GameObjectInfo info )
|
|
||||||
{
|
|
||||||
switch( info.ObjectType )
|
|
||||||
{
|
|
||||||
case ObjectType.Unknown:
|
|
||||||
case ObjectType.LoadingScreen:
|
|
||||||
case ObjectType.Map:
|
|
||||||
case ObjectType.Interface:
|
|
||||||
case ObjectType.Vfx:
|
|
||||||
case ObjectType.World:
|
|
||||||
case ObjectType.Housing:
|
|
||||||
case ObjectType.DemiHuman:
|
|
||||||
case ObjectType.Monster:
|
|
||||||
case ObjectType.Icon:
|
|
||||||
case ObjectType.Font:
|
|
||||||
// Don't do anything for these cases.
|
|
||||||
break;
|
|
||||||
case ObjectType.Accessory:
|
|
||||||
case ObjectType.Equipment:
|
|
||||||
FindEquipment( set, info );
|
|
||||||
break;
|
|
||||||
case ObjectType.Weapon:
|
|
||||||
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 ";
|
|
||||||
if( info.CustomizationType == CustomizationType.Skin )
|
|
||||||
{
|
|
||||||
set[ $"Customization: {raceString}{genderString}Skin Textures" ] = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var customizationString =
|
|
||||||
$"Customization: {race} {gender} {info.BodySlot} ({info.CustomizationType}) {info.PrimaryId}";
|
|
||||||
set[ customizationString ] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: throw new InvalidEnumArgumentException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void IdentifyVfx( IDictionary< string, object? > set, GamePath path )
|
|
||||||
{
|
|
||||||
var key = GameData.GamePathParser.VfxToKey( path );
|
|
||||||
if( key.Length == 0 || !_actions.TryGetValue( key, out var actions ) )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.MainHand:
|
||||||
case EquipSlot.OffHand:
|
case EquipSlot.OffHand:
|
||||||
|
case EquipSlot.BothHand:
|
||||||
|
if( item.ModelMain != 0 )
|
||||||
|
{
|
||||||
|
Add( weapons, WeaponKey( item, false ), item );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( item.ModelSub != 0 )
|
||||||
|
{
|
||||||
|
Add( weapons, WeaponKey( item, true ), item );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
// 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:
|
||||||
|
case EquipSlot.Hands:
|
||||||
|
case EquipSlot.Legs:
|
||||||
|
case EquipSlot.Feet:
|
||||||
|
case EquipSlot.BodyHands:
|
||||||
|
case EquipSlot.BodyHandsLegsFeet:
|
||||||
|
case EquipSlot.BodyLegsFeet:
|
||||||
|
case EquipSlot.FullBody:
|
||||||
|
case EquipSlot.HeadBody:
|
||||||
|
case EquipSlot.LegsFeet:
|
||||||
|
Add( equipment, 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() ) )
|
||||||
|
{
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
_weapons = weapons.Select( kvp => ( kvp.Key, kvp.Value ) ).ToList();
|
||||||
|
_equipment = equipment.Select( kvp => ( kvp.Key, kvp.Value ) ).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Comparer : IComparer< (ulong, HashSet< Item >) >
|
||||||
|
{
|
||||||
|
public int Compare( (ulong, HashSet< Item >) x, (ulong, HashSet< Item >) y )
|
||||||
|
=> x.Item1.CompareTo( y.Item1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int, int) FindIndexRange( List< (ulong, HashSet< Item >) > list, ulong key, ulong mask )
|
||||||
|
{
|
||||||
|
var maskedKey = key & mask;
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = ~idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
var endIdx = idx + 1;
|
||||||
|
while( endIdx < list.Count && maskedKey == ( list[ endIdx ].Item1 & mask ) )
|
||||||
|
{
|
||||||
|
++endIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ( idx, endIdx );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FindEquipment( IDictionary< string, object? > set, GameObjectInfo info )
|
||||||
|
{
|
||||||
|
var key = ( ulong )info.PrimaryId << 32;
|
||||||
|
var mask = 0xFFFF00000000ul;
|
||||||
|
if( info.EquipSlot != EquipSlot.Unknown )
|
||||||
|
{
|
||||||
|
key |= ( ulong )info.EquipSlot.ToSlot() << 16;
|
||||||
|
mask |= 0xFFFF0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( info.Variant != 0 )
|
||||||
|
{
|
||||||
|
key |= info.Variant;
|
||||||
|
mask |= 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (start, end) = FindIndexRange( _equipment, key, mask );
|
||||||
|
if( start == -1 )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for( ; start < end; ++start )
|
||||||
|
{
|
||||||
|
foreach( var item in _equipment[ start ].Item2 )
|
||||||
|
{
|
||||||
|
set[ item.Name.ToString() ] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FindWeapon( IDictionary< string, object? > set, GameObjectInfo info )
|
||||||
|
{
|
||||||
|
var key = ( ulong )info.PrimaryId << 32;
|
||||||
|
var mask = 0xFFFF00000000ul;
|
||||||
|
if( info.SecondaryId != 0 )
|
||||||
|
{
|
||||||
|
key |= ( ulong )info.SecondaryId << 16;
|
||||||
|
mask |= 0xFFFF0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( info.Variant != 0 )
|
||||||
|
{
|
||||||
|
key |= info.Variant;
|
||||||
|
mask |= 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (start, end) = FindIndexRange( _weapons, key, mask );
|
||||||
|
if( start == -1 )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for( ; start < end; ++start )
|
||||||
|
{
|
||||||
|
foreach( var item in _weapons[ start ].Item2 )
|
||||||
|
{
|
||||||
|
set[ item.Name.ToString() ] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
set[ data ] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IdentifyParsed( IDictionary< string, object? > set, GameObjectInfo info )
|
||||||
|
{
|
||||||
|
switch( info.ObjectType )
|
||||||
|
{
|
||||||
|
case ObjectType.Unknown:
|
||||||
|
switch( info.FileType )
|
||||||
{
|
{
|
||||||
var (begin, _) = FindIndexRange( _weapons, ( ( ulong )setId << 32 ) | ( ( ulong )weaponType << 16 ) | variant,
|
case FileType.Sound:
|
||||||
0xFFFFFFFFFFFF );
|
AddCounterString( set, FileType.Sound.ToString() );
|
||||||
return begin >= 0 ? _weapons[ begin ].Item2.FirstOrDefault() : null;
|
break;
|
||||||
|
case FileType.Animation:
|
||||||
|
case FileType.Pap:
|
||||||
|
AddCounterString( set, FileType.Animation.ToString() );
|
||||||
|
break;
|
||||||
|
case FileType.Shader:
|
||||||
|
AddCounterString( set, FileType.Shader.ToString() );
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
|
break;
|
||||||
|
case ObjectType.LoadingScreen:
|
||||||
|
case ObjectType.Map:
|
||||||
|
case ObjectType.Interface:
|
||||||
|
case ObjectType.Vfx:
|
||||||
|
case ObjectType.World:
|
||||||
|
case ObjectType.Housing:
|
||||||
|
case ObjectType.Font:
|
||||||
|
AddCounterString( set, info.ObjectType.ToString() );
|
||||||
|
break;
|
||||||
|
case ObjectType.DemiHuman:
|
||||||
|
set[ $"Demi Human: {info.PrimaryId}" ] = null;
|
||||||
|
break;
|
||||||
|
case ObjectType.Monster:
|
||||||
|
set[ $"Monster: {info.PrimaryId}" ] = null;
|
||||||
|
break;
|
||||||
|
case ObjectType.Icon:
|
||||||
|
set[ $"Icon: {info.IconId}" ] = null;
|
||||||
|
break;
|
||||||
|
case ObjectType.Accessory:
|
||||||
|
case ObjectType.Equipment:
|
||||||
|
FindEquipment( set, info );
|
||||||
|
break;
|
||||||
|
case ObjectType.Weapon:
|
||||||
|
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 ";
|
||||||
|
if( info.CustomizationType == CustomizationType.Skin )
|
||||||
{
|
{
|
||||||
var (begin, _) = FindIndexRange( _equipment,
|
set[ $"Customization: {raceString}{genderString}Skin Textures" ] = null;
|
||||||
( ( ulong )setId << 32 ) | ( ( ulong )slot.ToSlot() << 16 ) | variant,
|
|
||||||
0xFFFFFFFFFFFF );
|
|
||||||
return begin >= 0 ? _equipment[ begin ].Item2.FirstOrDefault() : null;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var customizationString =
|
||||||
|
$"Customization: {race} {gender} {info.BodySlot} ({info.CustomizationType}) {info.PrimaryId}";
|
||||||
|
set[ customizationString ] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: throw new InvalidEnumArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IdentifyVfx( IDictionary< string, object? > set, GamePath path )
|
||||||
|
{
|
||||||
|
var key = GameData.GamePathParser.VfxToKey( path );
|
||||||
|
if( key.Length == 0 || !_actions.TryGetValue( key, out var actions ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,158 +3,157 @@ using System.Runtime.InteropServices;
|
||||||
using Dalamud;
|
using Dalamud;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
namespace Penumbra.GameData.Structs
|
namespace Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public struct GameObjectInfo : IComparable
|
||||||
{
|
{
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
public static GameObjectInfo Equipment( FileType type, ushort setId, GenderRace gr = GenderRace.Unknown
|
||||||
public struct GameObjectInfo : IComparable
|
, EquipSlot slot = EquipSlot.Unknown, byte variant = 0 )
|
||||||
{
|
=> new()
|
||||||
public static GameObjectInfo Equipment( FileType type, ushort setId, GenderRace gr = GenderRace.Unknown
|
|
||||||
, EquipSlot slot = EquipSlot.Unknown, byte variant = 0 )
|
|
||||||
=> new()
|
|
||||||
{
|
|
||||||
FileType = type,
|
|
||||||
ObjectType = slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment,
|
|
||||||
PrimaryId = setId,
|
|
||||||
GenderRace = gr,
|
|
||||||
Variant = variant,
|
|
||||||
EquipSlot = slot,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static GameObjectInfo Weapon( FileType type, ushort setId, ushort weaponId, byte variant = 0 )
|
|
||||||
=> new()
|
|
||||||
{
|
|
||||||
FileType = type,
|
|
||||||
ObjectType = ObjectType.Weapon,
|
|
||||||
PrimaryId = setId,
|
|
||||||
SecondaryId = weaponId,
|
|
||||||
Variant = variant,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static GameObjectInfo Customization( FileType type, CustomizationType customizationType, ushort id = 0
|
|
||||||
, GenderRace gr = GenderRace.Unknown, BodySlot bodySlot = BodySlot.Unknown, byte variant = 0 )
|
|
||||||
=> new()
|
|
||||||
{
|
|
||||||
FileType = type,
|
|
||||||
ObjectType = ObjectType.Character,
|
|
||||||
PrimaryId = id,
|
|
||||||
GenderRace = gr,
|
|
||||||
BodySlot = bodySlot,
|
|
||||||
Variant = variant,
|
|
||||||
CustomizationType = customizationType,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static GameObjectInfo Monster( FileType type, ushort monsterId, ushort bodyId, byte variant = 0 )
|
|
||||||
=> new()
|
|
||||||
{
|
|
||||||
FileType = type,
|
|
||||||
ObjectType = ObjectType.Monster,
|
|
||||||
PrimaryId = monsterId,
|
|
||||||
SecondaryId = bodyId,
|
|
||||||
Variant = variant,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static GameObjectInfo DemiHuman( FileType type, ushort demiHumanId, ushort bodyId, EquipSlot slot = EquipSlot.Unknown,
|
|
||||||
byte variant = 0
|
|
||||||
)
|
|
||||||
=> new()
|
|
||||||
{
|
|
||||||
FileType = type,
|
|
||||||
ObjectType = ObjectType.DemiHuman,
|
|
||||||
PrimaryId = demiHumanId,
|
|
||||||
SecondaryId = bodyId,
|
|
||||||
Variant = variant,
|
|
||||||
EquipSlot = slot,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static GameObjectInfo Map( FileType type, byte c1, byte c2, byte c3, byte c4, byte variant, byte suffix = 0 )
|
|
||||||
=> new()
|
|
||||||
{
|
|
||||||
FileType = type,
|
|
||||||
ObjectType = ObjectType.Map,
|
|
||||||
MapC1 = c1,
|
|
||||||
MapC2 = c2,
|
|
||||||
MapC3 = c3,
|
|
||||||
MapC4 = c4,
|
|
||||||
MapSuffix = suffix,
|
|
||||||
Variant = variant,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static GameObjectInfo Icon( FileType type, uint iconId, bool hq, ClientLanguage lang = ClientLanguage.English )
|
|
||||||
=> new()
|
|
||||||
{
|
|
||||||
FileType = type,
|
|
||||||
ObjectType = ObjectType.Map,
|
|
||||||
IconId = iconId,
|
|
||||||
IconHq = hq,
|
|
||||||
Language = lang,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
[FieldOffset( 0 )]
|
|
||||||
public readonly ulong Identifier;
|
|
||||||
|
|
||||||
[FieldOffset( 0 )]
|
|
||||||
public FileType FileType;
|
|
||||||
|
|
||||||
[FieldOffset( 1 )]
|
|
||||||
public ObjectType ObjectType;
|
|
||||||
|
|
||||||
|
|
||||||
[FieldOffset( 2 )]
|
|
||||||
public ushort PrimaryId; // Equipment, Weapon, Customization, Monster, DemiHuman
|
|
||||||
|
|
||||||
[FieldOffset( 2 )]
|
|
||||||
public uint IconId; // Icon
|
|
||||||
|
|
||||||
[FieldOffset( 2 )]
|
|
||||||
public byte MapC1; // Map
|
|
||||||
|
|
||||||
[FieldOffset( 3 )]
|
|
||||||
public byte MapC2; // Map
|
|
||||||
|
|
||||||
[FieldOffset( 4 )]
|
|
||||||
public ushort SecondaryId; // Weapon, Monster, Demihuman
|
|
||||||
|
|
||||||
[FieldOffset( 4 )]
|
|
||||||
public byte MapC3; // Map
|
|
||||||
|
|
||||||
[FieldOffset( 4 )]
|
|
||||||
private byte _genderRaceByte; // Equipment, Customization
|
|
||||||
|
|
||||||
public GenderRace GenderRace
|
|
||||||
{
|
{
|
||||||
get => Names.GenderRaceFromByte( _genderRaceByte );
|
FileType = type,
|
||||||
set => _genderRaceByte = value.ToByte();
|
ObjectType = slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment,
|
||||||
}
|
PrimaryId = setId,
|
||||||
|
GenderRace = gr,
|
||||||
|
Variant = variant,
|
||||||
|
EquipSlot = slot,
|
||||||
|
};
|
||||||
|
|
||||||
[FieldOffset( 5 )]
|
public static GameObjectInfo Weapon( FileType type, ushort setId, ushort weaponId, byte variant = 0 )
|
||||||
public BodySlot BodySlot; // Customization
|
=> new()
|
||||||
|
{
|
||||||
|
FileType = type,
|
||||||
|
ObjectType = ObjectType.Weapon,
|
||||||
|
PrimaryId = setId,
|
||||||
|
SecondaryId = weaponId,
|
||||||
|
Variant = variant,
|
||||||
|
};
|
||||||
|
|
||||||
[FieldOffset( 5 )]
|
public static GameObjectInfo Customization( FileType type, CustomizationType customizationType, ushort id = 0
|
||||||
public byte MapC4; // Map
|
, GenderRace gr = GenderRace.Unknown, BodySlot bodySlot = BodySlot.Unknown, byte variant = 0 )
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
FileType = type,
|
||||||
|
ObjectType = ObjectType.Character,
|
||||||
|
PrimaryId = id,
|
||||||
|
GenderRace = gr,
|
||||||
|
BodySlot = bodySlot,
|
||||||
|
Variant = variant,
|
||||||
|
CustomizationType = customizationType,
|
||||||
|
};
|
||||||
|
|
||||||
[FieldOffset( 6 )]
|
public static GameObjectInfo Monster( FileType type, ushort monsterId, ushort bodyId, byte variant = 0 )
|
||||||
public byte Variant; // Equipment, Weapon, Customization, Map, Monster, Demihuman
|
=> new()
|
||||||
|
{
|
||||||
|
FileType = type,
|
||||||
|
ObjectType = ObjectType.Monster,
|
||||||
|
PrimaryId = monsterId,
|
||||||
|
SecondaryId = bodyId,
|
||||||
|
Variant = variant,
|
||||||
|
};
|
||||||
|
|
||||||
[FieldOffset( 6 )]
|
public static GameObjectInfo DemiHuman( FileType type, ushort demiHumanId, ushort bodyId, EquipSlot slot = EquipSlot.Unknown,
|
||||||
public bool IconHq; // Icon
|
byte variant = 0
|
||||||
|
)
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
FileType = type,
|
||||||
|
ObjectType = ObjectType.DemiHuman,
|
||||||
|
PrimaryId = demiHumanId,
|
||||||
|
SecondaryId = bodyId,
|
||||||
|
Variant = variant,
|
||||||
|
EquipSlot = slot,
|
||||||
|
};
|
||||||
|
|
||||||
[FieldOffset( 7 )]
|
public static GameObjectInfo Map( FileType type, byte c1, byte c2, byte c3, byte c4, byte variant, byte suffix = 0 )
|
||||||
public EquipSlot EquipSlot; // Equipment, Demihuman
|
=> new()
|
||||||
|
{
|
||||||
|
FileType = type,
|
||||||
|
ObjectType = ObjectType.Map,
|
||||||
|
MapC1 = c1,
|
||||||
|
MapC2 = c2,
|
||||||
|
MapC3 = c3,
|
||||||
|
MapC4 = c4,
|
||||||
|
MapSuffix = suffix,
|
||||||
|
Variant = variant,
|
||||||
|
};
|
||||||
|
|
||||||
[FieldOffset( 7 )]
|
public static GameObjectInfo Icon( FileType type, uint iconId, bool hq, bool hr, ClientLanguage lang = ClientLanguage.English )
|
||||||
public CustomizationType CustomizationType; // Customization
|
=> new()
|
||||||
|
{
|
||||||
|
FileType = type,
|
||||||
|
ObjectType = ObjectType.Icon,
|
||||||
|
IconId = iconId,
|
||||||
|
IconHqHr = ( byte )( hq ? hr ? 3 : 1 : hr ? 2 : 0 ),
|
||||||
|
Language = lang,
|
||||||
|
};
|
||||||
|
|
||||||
[FieldOffset( 7 )]
|
|
||||||
public ClientLanguage Language; // Icon
|
|
||||||
|
|
||||||
[FieldOffset( 7 )]
|
[FieldOffset( 0 )]
|
||||||
public byte MapSuffix;
|
public readonly ulong Identifier;
|
||||||
|
|
||||||
public override int GetHashCode()
|
[FieldOffset( 0 )]
|
||||||
=> Identifier.GetHashCode();
|
public FileType FileType;
|
||||||
|
|
||||||
public int CompareTo( object? r )
|
[FieldOffset( 1 )]
|
||||||
=> Identifier.CompareTo( r );
|
public ObjectType ObjectType;
|
||||||
|
|
||||||
|
|
||||||
|
[FieldOffset( 2 )]
|
||||||
|
public ushort PrimaryId; // Equipment, Weapon, Customization, Monster, DemiHuman
|
||||||
|
|
||||||
|
[FieldOffset( 2 )]
|
||||||
|
public uint IconId; // Icon
|
||||||
|
|
||||||
|
[FieldOffset( 2 )]
|
||||||
|
public byte MapC1; // Map
|
||||||
|
|
||||||
|
[FieldOffset( 3 )]
|
||||||
|
public byte MapC2; // Map
|
||||||
|
|
||||||
|
[FieldOffset( 4 )]
|
||||||
|
public ushort SecondaryId; // Weapon, Monster, Demihuman
|
||||||
|
|
||||||
|
[FieldOffset( 4 )]
|
||||||
|
public byte MapC3; // Map
|
||||||
|
|
||||||
|
[FieldOffset( 4 )]
|
||||||
|
private byte _genderRaceByte; // Equipment, Customization
|
||||||
|
|
||||||
|
public GenderRace GenderRace
|
||||||
|
{
|
||||||
|
get => Names.GenderRaceFromByte( _genderRaceByte );
|
||||||
|
set => _genderRaceByte = value.ToByte();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[FieldOffset( 5 )]
|
||||||
|
public BodySlot BodySlot; // Customization
|
||||||
|
|
||||||
|
[FieldOffset( 5 )]
|
||||||
|
public byte MapC4; // Map
|
||||||
|
|
||||||
|
[FieldOffset( 6 )]
|
||||||
|
public byte Variant; // Equipment, Weapon, Customization, Map, Monster, Demihuman
|
||||||
|
|
||||||
|
[FieldOffset( 6 )]
|
||||||
|
public byte IconHqHr; // Icon
|
||||||
|
|
||||||
|
[FieldOffset( 7 )]
|
||||||
|
public EquipSlot EquipSlot; // Equipment, Demihuman
|
||||||
|
|
||||||
|
[FieldOffset( 7 )]
|
||||||
|
public CustomizationType CustomizationType; // Customization
|
||||||
|
|
||||||
|
[FieldOffset( 7 )]
|
||||||
|
public ClientLanguage Language; // Icon
|
||||||
|
|
||||||
|
[FieldOffset( 7 )]
|
||||||
|
public byte MapSuffix;
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
=> Identifier.GetHashCode();
|
||||||
|
|
||||||
|
public int CompareTo( object? r )
|
||||||
|
=> Identifier.CompareTo( r );
|
||||||
}
|
}
|
||||||
|
|
@ -437,7 +437,11 @@ public partial class ModCollection
|
||||||
}
|
}
|
||||||
else if( !data.Item1.Contains( modPath.Mod ) )
|
else if( !data.Item1.Contains( modPath.Mod ) )
|
||||||
{
|
{
|
||||||
_changedItems[ name ] = ( data.Item1.Append( modPath.Mod ), obj );
|
_changedItems[ name ] = ( data.Item1.Append( modPath.Mod ), obj is int x && data.Item2 is int y ? x + y : obj);
|
||||||
|
}
|
||||||
|
else if (obj is int x && data.Item2 is int y)
|
||||||
|
{
|
||||||
|
_changedItems[name] = (data.Item1, x + y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ public partial class ModFileSystemSelector
|
||||||
0 => !( leaf.FullName().Contains( _modFilter.Lower, IgnoreCase ) || mod.Name.Contains( _modFilter ) ),
|
0 => !( leaf.FullName().Contains( _modFilter.Lower, IgnoreCase ) || mod.Name.Contains( _modFilter ) ),
|
||||||
1 => !mod.Name.Contains( _modFilter ),
|
1 => !mod.Name.Contains( _modFilter ),
|
||||||
2 => !mod.Author.Contains( _modFilter ),
|
2 => !mod.Author.Contains( _modFilter ),
|
||||||
3 => !mod.LowerChangedItemsString.Contains( _modFilter ),
|
3 => !mod.LowerChangedItemsString.Contains( _modFilter.Lower, IgnoreCase ),
|
||||||
_ => false, // Should never happen
|
_ => false, // Should never happen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,9 @@ public partial class ConfigWindow
|
||||||
{
|
{
|
||||||
// Functions in here for less pollution.
|
// Functions in here for less pollution.
|
||||||
bool FilterChangedItem( KeyValuePair< string, (SingleArray< Mod >, object?) > item )
|
bool FilterChangedItem( KeyValuePair< string, (SingleArray< Mod >, object?) > item )
|
||||||
=> ( _changedItemFilter.IsEmpty || item.Key.Contains( _changedItemFilter.Lower, StringComparison.InvariantCultureIgnoreCase ) )
|
=> ( _changedItemFilter.IsEmpty
|
||||||
|
|| ChangedItemName( item.Key, item.Value.Item2 )
|
||||||
|
.Contains( _changedItemFilter.Lower, StringComparison.InvariantCultureIgnoreCase ) )
|
||||||
&& ( _changedItemModFilter.IsEmpty || item.Value.Item1.Any( m => m.Name.Contains( _changedItemModFilter ) ) );
|
&& ( _changedItemModFilter.IsEmpty || item.Value.Item1.Any( m => m.Name.Contains( _changedItemModFilter ) ) );
|
||||||
|
|
||||||
void DrawChangedItemColumn( KeyValuePair< string, (SingleArray< Mod >, object?) > item )
|
void DrawChangedItemColumn( KeyValuePair< string, (SingleArray< Mod >, object?) > item )
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,16 @@ public partial class ConfigWindow
|
||||||
private static unsafe void Text( ResourceHandle* resource )
|
private static unsafe void Text( ResourceHandle* resource )
|
||||||
=> Text( resource->FileName(), resource->FileNameLength );
|
=> Text( resource->FileName(), resource->FileNameLength );
|
||||||
|
|
||||||
|
|
||||||
|
// Apply Changed Item Counters to the Name if necessary.
|
||||||
|
private static string ChangedItemName( string name, object? data )
|
||||||
|
=> data is int counter ? $"{counter} Files Manipulating {name}s" : name;
|
||||||
|
|
||||||
// Draw a changed item, invoking the Api-Events for clicks and tooltips.
|
// Draw a changed item, invoking the Api-Events for clicks and tooltips.
|
||||||
// Also draw the item Id in grey if requested
|
// Also draw the item Id in grey if requested
|
||||||
private void DrawChangedItem( string name, object? data, bool drawId )
|
private void DrawChangedItem( string name, object? data, bool drawId )
|
||||||
{
|
{
|
||||||
|
name = ChangedItemName( name, data );
|
||||||
var ret = ImGui.Selectable( name ) ? MouseButton.Left : MouseButton.None;
|
var ret = ImGui.Selectable( name ) ? MouseButton.Left : MouseButton.None;
|
||||||
ret = ImGui.IsItemClicked( ImGuiMouseButton.Right ) ? MouseButton.Right : ret;
|
ret = ImGui.IsItemClicked( ImGuiMouseButton.Right ) ? MouseButton.Right : ret;
|
||||||
ret = ImGui.IsItemClicked( ImGuiMouseButton.Middle ) ? MouseButton.Middle : ret;
|
ret = ImGui.IsItemClicked( ImGuiMouseButton.Middle ) ? MouseButton.Middle : ret;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue