mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Added object identification for equipment, weapons, action/animations and character customizations. Added mod filtering for changed items and authors. A bunch of bugfixes.
This commit is contained in:
parent
61be374b67
commit
2ff98f2338
21 changed files with 563 additions and 160 deletions
|
|
@ -24,6 +24,7 @@ namespace Penumbra.Game.Enums
|
||||||
{
|
{
|
||||||
return value switch
|
return value switch
|
||||||
{
|
{
|
||||||
|
CustomizationType.Body => "top",
|
||||||
CustomizationType.Face => "fac",
|
CustomizationType.Face => "fac",
|
||||||
CustomizationType.Iris => "iri",
|
CustomizationType.Iris => "iri",
|
||||||
CustomizationType.Accessory => "acc",
|
CustomizationType.Accessory => "acc",
|
||||||
|
|
@ -39,6 +40,7 @@ namespace Penumbra.Game.Enums
|
||||||
{
|
{
|
||||||
public static readonly Dictionary< string, CustomizationType > SuffixToCustomizationType = new()
|
public static readonly Dictionary< string, CustomizationType > SuffixToCustomizationType = new()
|
||||||
{
|
{
|
||||||
|
{ CustomizationType.Body.ToSuffix(), CustomizationType.Body },
|
||||||
{ CustomizationType.Face.ToSuffix(), CustomizationType.Face },
|
{ CustomizationType.Face.ToSuffix(), CustomizationType.Face },
|
||||||
{ CustomizationType.Iris.ToSuffix(), CustomizationType.Iris },
|
{ CustomizationType.Iris.ToSuffix(), CustomizationType.Iris },
|
||||||
{ CustomizationType.Accessory.ToSuffix(), CustomizationType.Accessory },
|
{ CustomizationType.Accessory.ToSuffix(), CustomizationType.Accessory },
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,10 @@ namespace Penumbra.Game.Enums
|
||||||
Feet = 8,
|
Feet = 8,
|
||||||
Ears = 9,
|
Ears = 9,
|
||||||
Neck = 10,
|
Neck = 10,
|
||||||
RingR = 12,
|
|
||||||
RingL = 14,
|
|
||||||
Wrists = 11,
|
Wrists = 11,
|
||||||
|
RingR = 12,
|
||||||
BothHand = 13,
|
BothHand = 13,
|
||||||
|
RingL = 14, // Not officially existing, means "weapon could be equipped in either hand" for the game.
|
||||||
HeadBody = 15,
|
HeadBody = 15,
|
||||||
BodyHandsLegsFeet = 16,
|
BodyHandsLegsFeet = 16,
|
||||||
SoulCrystal = 17,
|
SoulCrystal = 17,
|
||||||
|
|
@ -27,7 +27,7 @@ namespace Penumbra.Game.Enums
|
||||||
FullBody = 19,
|
FullBody = 19,
|
||||||
BodyHands = 20,
|
BodyHands = 20,
|
||||||
BodyLegsFeet = 21,
|
BodyLegsFeet = 21,
|
||||||
All = 22,
|
All = 22, // Not officially existing
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EquipSlotEnumExtension
|
public static class EquipSlotEnumExtension
|
||||||
|
|
@ -50,6 +50,35 @@ namespace Penumbra.Game.Enums
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static EquipSlot ToSlot( this EquipSlot value )
|
||||||
|
{
|
||||||
|
return value switch
|
||||||
|
{
|
||||||
|
EquipSlot.MainHand => EquipSlot.MainHand,
|
||||||
|
EquipSlot.Offhand => EquipSlot.Offhand,
|
||||||
|
EquipSlot.Head => EquipSlot.Head,
|
||||||
|
EquipSlot.Body => EquipSlot.Body,
|
||||||
|
EquipSlot.Hands => EquipSlot.Hands,
|
||||||
|
EquipSlot.Belt => EquipSlot.Belt,
|
||||||
|
EquipSlot.Legs => EquipSlot.Legs,
|
||||||
|
EquipSlot.Feet => EquipSlot.Feet,
|
||||||
|
EquipSlot.Ears => EquipSlot.Ears,
|
||||||
|
EquipSlot.Neck => EquipSlot.Neck,
|
||||||
|
EquipSlot.Wrists => EquipSlot.Wrists,
|
||||||
|
EquipSlot.RingR => EquipSlot.RingR,
|
||||||
|
EquipSlot.BothHand => EquipSlot.MainHand,
|
||||||
|
EquipSlot.RingL => EquipSlot.RingR,
|
||||||
|
EquipSlot.HeadBody => EquipSlot.Body,
|
||||||
|
EquipSlot.BodyHandsLegsFeet => EquipSlot.Body,
|
||||||
|
EquipSlot.SoulCrystal => EquipSlot.SoulCrystal,
|
||||||
|
EquipSlot.LegsFeet => EquipSlot.Legs,
|
||||||
|
EquipSlot.FullBody => EquipSlot.Body,
|
||||||
|
EquipSlot.BodyHands => EquipSlot.Body,
|
||||||
|
EquipSlot.BodyLegsFeet => EquipSlot.Body,
|
||||||
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsEquipment( this EquipSlot value )
|
public static bool IsEquipment( this EquipSlot value )
|
||||||
{
|
{
|
||||||
return value switch
|
return value switch
|
||||||
|
|
|
||||||
|
|
@ -64,18 +64,18 @@ namespace Penumbra.Game.Enums
|
||||||
ElezenMaleNpc = 0504,
|
ElezenMaleNpc = 0504,
|
||||||
ElezenFemale = 0601,
|
ElezenFemale = 0601,
|
||||||
ElezenFemaleNpc = 0604,
|
ElezenFemaleNpc = 0604,
|
||||||
LalafellMale = 0701,
|
MiqoteMale = 0701,
|
||||||
LalafellMaleNpc = 0704,
|
MiqoteMaleNpc = 0704,
|
||||||
LalafellFemale = 0801,
|
MiqoteFemale = 0801,
|
||||||
LalafellFemaleNpc = 0804,
|
MiqoteFemaleNpc = 0804,
|
||||||
MiqoteMale = 0901,
|
RoegadynMale = 0901,
|
||||||
MiqoteMaleNpc = 0904,
|
RoegadynMaleNpc = 0904,
|
||||||
MiqoteFemale = 1001,
|
RoegadynFemale = 1001,
|
||||||
MiqoteFemaleNpc = 1004,
|
RoegadynFemaleNpc = 1004,
|
||||||
RoegadynMale = 1101,
|
LalafellMale = 1101,
|
||||||
RoegadynMaleNpc = 1104,
|
LalafellMaleNpc = 1104,
|
||||||
RoegadynFemale = 1201,
|
LalafellFemale = 1201,
|
||||||
RoegadynFemaleNpc = 1204,
|
LalafellFemaleNpc = 1204,
|
||||||
AuRaMale = 1301,
|
AuRaMale = 1301,
|
||||||
AuRaMaleNpc = 1304,
|
AuRaMaleNpc = 1304,
|
||||||
AuRaFemale = 1401,
|
AuRaFemale = 1401,
|
||||||
|
|
@ -158,6 +158,7 @@ namespace Penumbra.Game.Enums
|
||||||
{
|
{
|
||||||
return value switch
|
return value switch
|
||||||
{
|
{
|
||||||
|
GenderRace.Unknown => ( Gender.Unknown, Race.Unknown ),
|
||||||
GenderRace.MidlanderMale => ( Gender.Male, Race.Midlander ),
|
GenderRace.MidlanderMale => ( Gender.Male, Race.Midlander ),
|
||||||
GenderRace.MidlanderMaleNpc => ( Gender.MaleNpc, Race.Midlander ),
|
GenderRace.MidlanderMaleNpc => ( Gender.MaleNpc, Race.Midlander ),
|
||||||
GenderRace.MidlanderFemale => ( Gender.Female, Race.Midlander ),
|
GenderRace.MidlanderFemale => ( Gender.Female, Race.Midlander ),
|
||||||
|
|
@ -215,18 +216,18 @@ namespace Penumbra.Game.Enums
|
||||||
GenderRace.ElezenMaleNpc => "0504",
|
GenderRace.ElezenMaleNpc => "0504",
|
||||||
GenderRace.ElezenFemale => "0601",
|
GenderRace.ElezenFemale => "0601",
|
||||||
GenderRace.ElezenFemaleNpc => "0604",
|
GenderRace.ElezenFemaleNpc => "0604",
|
||||||
GenderRace.LalafellMale => "0701",
|
GenderRace.MiqoteMale => "0701",
|
||||||
GenderRace.LalafellMaleNpc => "0704",
|
GenderRace.MiqoteMaleNpc => "0704",
|
||||||
GenderRace.LalafellFemale => "0801",
|
GenderRace.MiqoteFemale => "0801",
|
||||||
GenderRace.LalafellFemaleNpc => "0804",
|
GenderRace.MiqoteFemaleNpc => "0804",
|
||||||
GenderRace.MiqoteMale => "0901",
|
GenderRace.RoegadynMale => "0901",
|
||||||
GenderRace.MiqoteMaleNpc => "0904",
|
GenderRace.RoegadynMaleNpc => "0904",
|
||||||
GenderRace.MiqoteFemale => "1001",
|
GenderRace.RoegadynFemale => "1001",
|
||||||
GenderRace.MiqoteFemaleNpc => "1004",
|
GenderRace.RoegadynFemaleNpc => "1004",
|
||||||
GenderRace.RoegadynMale => "1101",
|
GenderRace.LalafellMale => "1101",
|
||||||
GenderRace.RoegadynMaleNpc => "1104",
|
GenderRace.LalafellMaleNpc => "1104",
|
||||||
GenderRace.RoegadynFemale => "1201",
|
GenderRace.LalafellFemale => "1201",
|
||||||
GenderRace.RoegadynFemaleNpc => "1204",
|
GenderRace.LalafellFemaleNpc => "1204",
|
||||||
GenderRace.AuRaMale => "1301",
|
GenderRace.AuRaMale => "1301",
|
||||||
GenderRace.AuRaMaleNpc => "1304",
|
GenderRace.AuRaMaleNpc => "1304",
|
||||||
GenderRace.AuRaFemale => "1401",
|
GenderRace.AuRaFemale => "1401",
|
||||||
|
|
@ -260,18 +261,18 @@ namespace Penumbra.Game.Enums
|
||||||
"0504" => GenderRace.ElezenMaleNpc,
|
"0504" => GenderRace.ElezenMaleNpc,
|
||||||
"0601" => GenderRace.ElezenFemale,
|
"0601" => GenderRace.ElezenFemale,
|
||||||
"0604" => GenderRace.ElezenFemaleNpc,
|
"0604" => GenderRace.ElezenFemaleNpc,
|
||||||
"0701" => GenderRace.LalafellMale,
|
"0701" => GenderRace.MiqoteMale,
|
||||||
"0704" => GenderRace.LalafellMaleNpc,
|
"0704" => GenderRace.MiqoteMaleNpc,
|
||||||
"0801" => GenderRace.LalafellFemale,
|
"0801" => GenderRace.MiqoteFemale,
|
||||||
"0804" => GenderRace.LalafellFemaleNpc,
|
"0804" => GenderRace.MiqoteFemaleNpc,
|
||||||
"0901" => GenderRace.MiqoteMale,
|
"0901" => GenderRace.RoegadynMale,
|
||||||
"0904" => GenderRace.MiqoteMaleNpc,
|
"0904" => GenderRace.RoegadynMaleNpc,
|
||||||
"1001" => GenderRace.MiqoteFemale,
|
"1001" => GenderRace.RoegadynFemale,
|
||||||
"1004" => GenderRace.MiqoteFemaleNpc,
|
"1004" => GenderRace.RoegadynFemaleNpc,
|
||||||
"1101" => GenderRace.RoegadynMale,
|
"1101" => GenderRace.LalafellMale,
|
||||||
"1104" => GenderRace.RoegadynMaleNpc,
|
"1104" => GenderRace.LalafellMaleNpc,
|
||||||
"1201" => GenderRace.RoegadynFemale,
|
"1201" => GenderRace.LalafellFemale,
|
||||||
"1204" => GenderRace.RoegadynFemaleNpc,
|
"1204" => GenderRace.LalafellFemaleNpc,
|
||||||
"1301" => GenderRace.AuRaMale,
|
"1301" => GenderRace.AuRaMale,
|
||||||
"1304" => GenderRace.AuRaMaleNpc,
|
"1304" => GenderRace.AuRaMaleNpc,
|
||||||
"1401" => GenderRace.AuRaFemale,
|
"1401" => GenderRace.AuRaFemale,
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,9 @@ namespace Penumbra.Game
|
||||||
Variant = variant,
|
Variant = variant,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static GameObjectInfo DemiHuman( FileType type, ushort demiHumanId, ushort bodyId, byte variant = 0,
|
public static GameObjectInfo DemiHuman( FileType type, ushort demiHumanId, ushort bodyId, EquipSlot slot = EquipSlot.Unknown,
|
||||||
EquipSlot slot = EquipSlot.Unknown )
|
byte variant = 0
|
||||||
|
)
|
||||||
=> new()
|
=> new()
|
||||||
{
|
{
|
||||||
FileType = type,
|
FileType = type,
|
||||||
|
|
@ -88,6 +89,7 @@ namespace Penumbra.Game
|
||||||
Language = lang,
|
Language = lang,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
[FieldOffset( 0 )]
|
[FieldOffset( 0 )]
|
||||||
public readonly ulong Identifier;
|
public readonly ulong Identifier;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,30 +35,30 @@ namespace Penumbra.Game
|
||||||
, { 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*)\.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(?'weapon'\d{4})/obj/body/b(?'id'\d{4})/texture/v(?'variant'\d{2})_w\k'weapon'b\k'id'(_[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") } }
|
||||||
, { 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.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.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.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'(--)?)c\k'race'\k'typeabr'\k'id'_(?'slot'[a-z]{3})(_[a-z])?_[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/common/texture/skin(?'skin'.*)\.tex")
|
, new(@"chara/common/texture/skin(?'skin'.*)\.tex")
|
||||||
, new(@"chara/common/texture/decal_(?'location'[a-z]+)/[-_]?decal_(?'id'\d+).tex") } } } }
|
, new(@"chara/common/texture/decal_(?'location'[a-z]+)/[-_]?decal_(?'id'\d+).tex") } } } }
|
||||||
, { FileType.Model, new Dictionary< ObjectType, Regex[] >()
|
, { FileType.Model, new Dictionary< ObjectType, Regex[] >()
|
||||||
{ { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'weapon'\d{4})/obj/body/b(?'id'\d{4})/model/w\k'weapon'b\k'id'\.mdl") } }
|
{ { 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.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.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.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.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") } } } }
|
, { 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[] >()
|
, { FileType.Material, new Dictionary< ObjectType, Regex[] >()
|
||||||
{ { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'weapon'\d{4})/obj/body/b(?'id'\d{4})/material/v(?'variant'\d{4})/mt_w\k'weapon'b\k'id'_[a-z]\.mtrl") } }
|
{ { 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.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.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(?'weapon'\d{4})/obj/body/b(?'id'\d{4})/b\k'id'\.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") } }
|
||||||
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/e\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.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/e\k'equip'\.imc") } }
|
||||||
|
|
@ -228,16 +228,21 @@ namespace Penumbra.Game
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var demiHumanId = ushort.Parse( groups[ "monster" ].Value );
|
var demiHumanId = ushort.Parse( groups[ "id" ].Value );
|
||||||
var bodyId = ushort.Parse( groups[ "id" ].Value );
|
var equipId = ushort.Parse( groups[ "equip" ].Value );
|
||||||
if( fileType == FileType.Imc )
|
if( fileType == FileType.Imc )
|
||||||
{
|
{
|
||||||
return GameObjectInfo.DemiHuman( fileType, demiHumanId, bodyId );
|
return GameObjectInfo.DemiHuman( fileType, demiHumanId, equipId );
|
||||||
}
|
}
|
||||||
|
|
||||||
var slot = GameData.SuffixToEquipSlot[ groups[ "slot" ].Value ];
|
var slot = GameData.SuffixToEquipSlot[ groups[ "slot" ].Value ];
|
||||||
|
if( fileType == FileType.Model )
|
||||||
|
{
|
||||||
|
return GameObjectInfo.DemiHuman( fileType, demiHumanId, equipId, slot );
|
||||||
|
}
|
||||||
|
|
||||||
var variant = byte.Parse( groups[ "variant" ].Value );
|
var variant = byte.Parse( groups[ "variant" ].Value );
|
||||||
return GameObjectInfo.DemiHuman( fileType, demiHumanId, bodyId, variant, slot );
|
return GameObjectInfo.DemiHuman( fileType, demiHumanId, equipId, slot, variant );
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
{
|
{
|
||||||
|
|
@ -265,7 +270,7 @@ namespace Penumbra.Game
|
||||||
|
|
||||||
var gr = GameData.GenderRaceFromCode( groups[ "race" ].Value );
|
var gr = GameData.GenderRaceFromCode( groups[ "race" ].Value );
|
||||||
var bodySlot = GameData.StringToBodySlot[ groups[ "type" ].Value ];
|
var bodySlot = GameData.StringToBodySlot[ groups[ "type" ].Value ];
|
||||||
var type = GameData.SuffixToCustomizationType[ groups[ "slot" ].Value ];
|
var type = groups[ "slot" ].Success ? GameData.SuffixToCustomizationType[ groups[ "slot" ].Value ] : CustomizationType.Skin;
|
||||||
if( fileType == FileType.Material )
|
if( fileType == FileType.Material )
|
||||||
{
|
{
|
||||||
var variant = byte.Parse( groups[ "variant" ].Value );
|
var variant = byte.Parse( groups[ "variant" ].Value );
|
||||||
|
|
@ -361,24 +366,19 @@ namespace Penumbra.Game
|
||||||
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
|
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsTailTexture( GameObjectInfo info )
|
private static readonly Regex VfxRegexTmb = new( @"chara/action/(?'key'[^\s]+?)\.tmb" );
|
||||||
|
private static readonly Regex VfxRegexPap = new( @"chara/human/c0101/animation/a0001/[^\s]+?/(?'key'[^\s]+?)\.pap" );
|
||||||
|
|
||||||
|
public static string VfxToKey( GamePath path )
|
||||||
{
|
{
|
||||||
if( info.ObjectType != ObjectType.Character )
|
var match = VfxRegexTmb.Match( path );
|
||||||
|
if( match.Success )
|
||||||
{
|
{
|
||||||
return false;
|
return match.Groups[ "key" ].Value.ToLowerInvariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
return info.BodySlot == BodySlot.Tail && info.FileType == FileType.Texture;
|
match = VfxRegexPap.Match( path );
|
||||||
}
|
return match.Success ? match.Groups[ "key" ].Value.ToLowerInvariant() : string.Empty;
|
||||||
|
|
||||||
public static bool IsSkinTexture( GameObjectInfo info )
|
|
||||||
{
|
|
||||||
if( info.ObjectType != ObjectType.Character )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return info.FileType == FileType.Texture && info.CustomizationType == CustomizationType.Skin;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
290
Penumbra/Game/ObjectIdentification.cs
Normal file
290
Penumbra/Game/ObjectIdentification.cs
Normal file
|
|
@ -0,0 +1,290 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using Penumbra.Game.Enums;
|
||||||
|
using Penumbra.Util;
|
||||||
|
using Action = Lumina.Excel.GeneratedSheets.Action;
|
||||||
|
|
||||||
|
namespace Penumbra.Game
|
||||||
|
{
|
||||||
|
public class ObjectIdentification
|
||||||
|
{
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_actions[ key ] = new HashSet< Action > { action };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectIdentification( DalamudPluginInterface plugin )
|
||||||
|
{
|
||||||
|
var items = plugin.Data.GetExcelSheet< Item >( plugin.ClientState.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.RingR:
|
||||||
|
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 plugin.Data.GetExcelSheet< Action >( plugin.ClientState.ClientLanguage ) )
|
||||||
|
{
|
||||||
|
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( 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:
|
||||||
|
if( info.CustomizationType == CustomizationType.Skin )
|
||||||
|
{
|
||||||
|
set[ "Customization: Player Skin" ] = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var (gender, race) = info.GenderRace.Split();
|
||||||
|
var customizationString =
|
||||||
|
$"Customization: {race} {gender}s {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 = 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 = GamePathParser.GetFileInfo( path );
|
||||||
|
IdentifyParsed( set, info );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,9 @@ namespace Penumbra.Game
|
||||||
|
|
||||||
private readonly float[] Attributes;
|
private readonly float[] Attributes;
|
||||||
|
|
||||||
|
public RspEntry( RspEntry copy )
|
||||||
|
=> Attributes = ( float[] )copy.Attributes.Clone();
|
||||||
|
|
||||||
public RspEntry( byte[] bytes, int offset )
|
public RspEntry( byte[] bytes, int offset )
|
||||||
{
|
{
|
||||||
if( offset < 0 || offset + ByteSize > bytes.Length )
|
if( offset < 0 || offset + ByteSize > bytes.Length )
|
||||||
|
|
|
||||||
|
|
@ -197,11 +197,12 @@ namespace Penumbra.Importer
|
||||||
|
|
||||||
public static DirectoryInfo CreateModFolder( DirectoryInfo outDirectory, string modListName )
|
public static DirectoryInfo CreateModFolder( DirectoryInfo outDirectory, string modListName )
|
||||||
{
|
{
|
||||||
var newModFolder = NewOptionDirectory( outDirectory, Path.GetFileName( modListName ) );
|
var newModFolderBase = NewOptionDirectory( outDirectory, Path.GetFileName( modListName ) );
|
||||||
|
var newModFolder = newModFolderBase;
|
||||||
var i = 2;
|
var i = 2;
|
||||||
while( newModFolder.Exists && i < 12 )
|
while( newModFolder.Exists && i < 12 )
|
||||||
{
|
{
|
||||||
newModFolder = new DirectoryInfo( newModFolder.FullName + $" ({i++})" );
|
newModFolder = new DirectoryInfo( newModFolderBase.FullName + $" ({i++})" );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( newModFolder.Exists )
|
if( newModFolder.Exists )
|
||||||
|
|
@ -349,7 +350,7 @@ namespace Penumbra.Importer
|
||||||
TotalProgress += wtf.LongCount();
|
TotalProgress += wtf.LongCount();
|
||||||
|
|
||||||
// Extract each SimpleMod into the new mod folder
|
// Extract each SimpleMod into the new mod folder
|
||||||
foreach( var simpleMod in wtf.Where( M => M != null ) )
|
foreach( var simpleMod in wtf.Where( m => m != null ) )
|
||||||
{
|
{
|
||||||
ExtractMod( outDirectory, simpleMod, dataStream );
|
ExtractMod( outDirectory, simpleMod, dataStream );
|
||||||
CurrentProgress++;
|
CurrentProgress++;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Penumbra.Game;
|
using Penumbra.Game;
|
||||||
|
|
@ -13,7 +12,7 @@ namespace Penumbra.Meta.Files
|
||||||
private const int RacialScalingStart = 0x2A800;
|
private const int RacialScalingStart = 0x2A800;
|
||||||
|
|
||||||
private readonly byte[] _byteData = new byte[RacialScalingStart];
|
private readonly byte[] _byteData = new byte[RacialScalingStart];
|
||||||
private readonly List< RspEntry > _rspEntries;
|
private readonly RspEntry[] _rspEntries;
|
||||||
|
|
||||||
public CmpFile( byte[] bytes )
|
public CmpFile( byte[] bytes )
|
||||||
{
|
{
|
||||||
|
|
@ -24,11 +23,13 @@ namespace Penumbra.Meta.Files
|
||||||
|
|
||||||
Array.Copy( bytes, _byteData, RacialScalingStart );
|
Array.Copy( bytes, _byteData, RacialScalingStart );
|
||||||
var rspEntryNum = ( bytes.Length - RacialScalingStart ) / RspEntry.ByteSize;
|
var rspEntryNum = ( bytes.Length - RacialScalingStart ) / RspEntry.ByteSize;
|
||||||
_rspEntries = new List< RspEntry >( rspEntryNum );
|
var tmp = new List< RspEntry >( rspEntryNum );
|
||||||
for( var i = 0; i < rspEntryNum; ++i )
|
for( var i = 0; i < rspEntryNum; ++i )
|
||||||
{
|
{
|
||||||
_rspEntries.Add( new RspEntry( bytes, RacialScalingStart + i * RspEntry.ByteSize ) );
|
tmp.Add( new RspEntry( bytes, RacialScalingStart + i * RspEntry.ByteSize ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_rspEntries = tmp.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public RspEntry this[ SubRace subRace ]
|
public RspEntry this[ SubRace subRace ]
|
||||||
|
|
@ -49,7 +50,7 @@ namespace Penumbra.Meta.Files
|
||||||
|
|
||||||
public byte[] WriteBytes()
|
public byte[] WriteBytes()
|
||||||
{
|
{
|
||||||
using var s = new MemoryStream( RacialScalingStart + _rspEntries.Count * RspEntry.ByteSize );
|
using var s = new MemoryStream( RacialScalingStart + _rspEntries.Length * RspEntry.ByteSize );
|
||||||
s.Write( _byteData, 0, _byteData.Length );
|
s.Write( _byteData, 0, _byteData.Length );
|
||||||
foreach( var entry in _rspEntries )
|
foreach( var entry in _rspEntries )
|
||||||
{
|
{
|
||||||
|
|
@ -60,10 +61,10 @@ namespace Penumbra.Meta.Files
|
||||||
return s.ToArray();
|
return s.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private CmpFile( byte[] data, List< RspEntry > entries )
|
private CmpFile( byte[] data, RspEntry[] entries )
|
||||||
{
|
{
|
||||||
_byteData = data.ToArray();
|
_byteData = data.ToArray();
|
||||||
_rspEntries = entries.ToList();
|
_rspEntries = entries.Select( e => new RspEntry( e ) ).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CmpFile Clone()
|
public CmpFile Clone()
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ namespace Penumbra.Meta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Reset( bool reload )
|
public void Reset( bool reload = true )
|
||||||
{
|
{
|
||||||
foreach( var file in _currentFiles )
|
foreach( var file in _currentFiles )
|
||||||
{
|
{
|
||||||
|
|
@ -90,9 +90,6 @@ namespace Penumbra.Meta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset()
|
|
||||||
=> Reset( true );
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
=> Reset();
|
=> Reset();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
using Penumbra.Game;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mod
|
namespace Penumbra.Mod
|
||||||
{
|
{
|
||||||
|
|
@ -12,7 +16,7 @@ namespace Penumbra.Mod
|
||||||
public ModMeta Meta;
|
public ModMeta Meta;
|
||||||
public ModResources Resources;
|
public ModResources Resources;
|
||||||
public string SortOrder;
|
public string SortOrder;
|
||||||
|
public SortedList< string, object? > ChangedItems { get; } = new();
|
||||||
public FileInfo MetaFile { get; set; }
|
public FileInfo MetaFile { get; set; }
|
||||||
|
|
||||||
private ModData( DirectoryInfo basePath, ModMeta meta, ModResources resources )
|
private ModData( DirectoryInfo basePath, ModMeta meta, ModResources resources )
|
||||||
|
|
@ -22,6 +26,26 @@ namespace Penumbra.Mod
|
||||||
Resources = resources;
|
Resources = resources;
|
||||||
MetaFile = MetaFileInfo( basePath );
|
MetaFile = MetaFileInfo( basePath );
|
||||||
SortOrder = meta.Name;
|
SortOrder = meta.Name;
|
||||||
|
ComputeChangedItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ComputeChangedItems()
|
||||||
|
{
|
||||||
|
var ident = Service< ObjectIdentification >.Get();
|
||||||
|
|
||||||
|
ChangedItems.Clear();
|
||||||
|
foreach( var file in Resources.ModFiles.Select( f => new RelPath( f, BasePath ) ) )
|
||||||
|
{
|
||||||
|
foreach( var path in ModFunctions.GetAllFiles( file, Meta ) )
|
||||||
|
{
|
||||||
|
ident.Identify( ChangedItems, path );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var path in Meta.FileSwaps.Keys )
|
||||||
|
{
|
||||||
|
ident.Identify( ChangedItems, path );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileInfo MetaFileInfo( DirectoryInfo basePath )
|
public static FileInfo MetaFileInfo( DirectoryInfo basePath )
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,25 @@ namespace Penumbra.Mod
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HashSet< GamePath > GetAllFiles( RelPath relPath, ModMeta meta )
|
||||||
|
{
|
||||||
|
var ret = new HashSet< GamePath >();
|
||||||
|
foreach( var option in meta.Groups.Values.SelectMany( g => g.Options ) )
|
||||||
|
{
|
||||||
|
if( option.OptionFiles.TryGetValue( relPath, out var files ) )
|
||||||
|
{
|
||||||
|
ret.UnionWith( files );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ret.Count == 0 )
|
||||||
|
{
|
||||||
|
ret.Add( new GamePath( relPath, 0 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
public static ModSettings ConvertNamedSettings( NamedModSettings namedSettings, ModMeta meta )
|
public static ModSettings ConvertNamedSettings( NamedModSettings namedSettings, ModMeta meta )
|
||||||
{
|
{
|
||||||
ModSettings ret = new()
|
ModSettings ret = new()
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ namespace Penumbra.Mod
|
||||||
public string Description { get; set; } = "";
|
public string Description { get; set; } = "";
|
||||||
public string Version { get; set; } = "";
|
public string Version { get; set; } = "";
|
||||||
public string Website { get; set; } = "";
|
public string Website { get; set; } = "";
|
||||||
public List< string > ChangedItems { get; set; } = new();
|
|
||||||
|
|
||||||
[JsonProperty( ItemConverterType = typeof( GamePathConverter ) )]
|
[JsonProperty( ItemConverterType = typeof( GamePathConverter ) )]
|
||||||
public Dictionary< GamePath, GamePath > FileSwaps { get; set; } = new();
|
public Dictionary< GamePath, GamePath > FileSwaps { get; set; } = new();
|
||||||
|
|
@ -50,7 +49,6 @@ namespace Penumbra.Mod
|
||||||
Description = newMeta.Description;
|
Description = newMeta.Description;
|
||||||
Version = newMeta.Version;
|
Version = newMeta.Version;
|
||||||
Website = newMeta.Website;
|
Website = newMeta.Website;
|
||||||
ChangedItems = newMeta.ChangedItems;
|
|
||||||
FileSwaps = newMeta.FileSwaps;
|
FileSwaps = newMeta.FileSwaps;
|
||||||
Groups = newMeta.Groups;
|
Groups = newMeta.Groups;
|
||||||
FileHash = newMeta.FileHash;
|
FileHash = newMeta.FileHash;
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ namespace Penumbra.Mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Update the current set of files used by the mod,
|
// Update the current set of files used by the mod,
|
||||||
// returns true if anything changed.
|
// returns true if anything changed.
|
||||||
public ResourceChange RefreshModFiles( DirectoryInfo basePath )
|
public ResourceChange RefreshModFiles( DirectoryInfo basePath )
|
||||||
|
|
@ -70,13 +69,13 @@ namespace Penumbra.Mod
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceChange changes = 0;
|
ResourceChange changes = 0;
|
||||||
if( !tmpFiles.SequenceEqual( ModFiles ) )
|
if( !tmpFiles.Select( f => f.FullName ).SequenceEqual( ModFiles.Select( f => f.FullName ) ) )
|
||||||
{
|
{
|
||||||
ModFiles = tmpFiles;
|
ModFiles = tmpFiles;
|
||||||
changes |= ResourceChange.Files;
|
changes |= ResourceChange.Files;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !tmpMetas.SequenceEqual( MetaFiles ) )
|
if( !tmpMetas.Select( f => f.FullName ).SequenceEqual( MetaFiles.Select( f => f.FullName ) ) )
|
||||||
{
|
{
|
||||||
MetaFiles = tmpMetas;
|
MetaFiles = tmpMetas;
|
||||||
changes |= ResourceChange.Meta;
|
changes |= ResourceChange.Meta;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ namespace Penumbra.Mods
|
||||||
|
|
||||||
public void SortMods()
|
public void SortMods()
|
||||||
{
|
{
|
||||||
AvailableMods.Sort( ( m1, m2 ) => string.Compare( m1.Data.SortOrder, m2.Data.SortOrder, StringComparison.InvariantCultureIgnoreCase ) );
|
AvailableMods.Sort( ( m1, m2 )
|
||||||
|
=> string.Compare( m1.Data.SortOrder, m2.Data.SortOrder, StringComparison.InvariantCultureIgnoreCase ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddFiles( Dictionary< GamePath, Mod.Mod > registeredFiles, Mod.Mod mod )
|
private void AddFiles( Dictionary< GamePath, Mod.Mod > registeredFiles, Mod.Mod mod )
|
||||||
|
|
@ -79,7 +80,7 @@ namespace Penumbra.Mods
|
||||||
|
|
||||||
public void UpdateMetaManipulations()
|
public void UpdateMetaManipulations()
|
||||||
{
|
{
|
||||||
MetaManipulations.Reset();
|
MetaManipulations.Reset( false );
|
||||||
|
|
||||||
foreach( var mod in AvailableMods.Where( m => m.Settings.Enabled && m.Data.Resources.MetaManipulations.Count > 0 )
|
foreach( var mod in AvailableMods.Where( m => m.Settings.Enabled && m.Data.Resources.MetaManipulations.Count > 0 )
|
||||||
.OrderByDescending( m => m.Settings.Priority ) )
|
.OrderByDescending( m => m.Settings.Priority ) )
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,11 @@ namespace Penumbra.Mods
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( metaChanges || fileChanges.HasFlag( ResourceChange.Files ) )
|
||||||
|
{
|
||||||
|
mod.ComputeChangedItems();
|
||||||
|
}
|
||||||
|
|
||||||
var nameChange = !string.Equals( oldName, mod.Meta.Name, StringComparison.InvariantCulture );
|
var nameChange = !string.Equals( oldName, mod.Meta.Name, StringComparison.InvariantCulture );
|
||||||
|
|
||||||
recomputeMeta |= fileChanges.HasFlag( ResourceChange.Meta );
|
recomputeMeta |= fileChanges.HasFlag( ResourceChange.Meta );
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,12 @@
|
||||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
|
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Lumina.Excel">
|
||||||
|
<HintPath>$(DALAMUD_ROOT)\Lumina.Excel.dll</HintPath>
|
||||||
|
<HintPath>..\libs\Lumina.Excel.dll</HintPath>
|
||||||
|
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using System.Linq;
|
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using EmbedIO;
|
using EmbedIO;
|
||||||
|
|
@ -41,6 +39,7 @@ namespace Penumbra
|
||||||
{
|
{
|
||||||
PluginInterface = pluginInterface;
|
PluginInterface = pluginInterface;
|
||||||
Service< DalamudPluginInterface >.Set( PluginInterface );
|
Service< DalamudPluginInterface >.Set( PluginInterface );
|
||||||
|
Service< ObjectIdentification >.Set( PluginInterface );
|
||||||
|
|
||||||
Configuration = Configuration.Load( PluginInterface );
|
Configuration = Configuration.Load( PluginInterface );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Penumbra.Game.Enums;
|
using Penumbra.Game.Enums;
|
||||||
using Penumbra.Meta;
|
using Penumbra.Meta;
|
||||||
using Penumbra.Mod;
|
using Penumbra.Mod;
|
||||||
|
|
@ -37,8 +38,6 @@ namespace Penumbra.UI
|
||||||
private const string LabelAboutTab = "About";
|
private const string LabelAboutTab = "About";
|
||||||
private const string LabelChangedItemsTab = "Changed Items";
|
private const string LabelChangedItemsTab = "Changed Items";
|
||||||
private const string LabelChangedItemsHeader = "##changedItems";
|
private const string LabelChangedItemsHeader = "##changedItems";
|
||||||
private const string LabelChangedItemIdx = "##citem_";
|
|
||||||
private const string LabelChangedItemNew = "##citem_new";
|
|
||||||
private const string LabelConflictsTab = "Mod Conflicts";
|
private const string LabelConflictsTab = "Mod Conflicts";
|
||||||
private const string LabelConflictsHeader = "##conflicts";
|
private const string LabelConflictsHeader = "##conflicts";
|
||||||
private const string LabelFileSwapTab = "File Swaps";
|
private const string LabelFileSwapTab = "File Swaps";
|
||||||
|
|
@ -64,7 +63,6 @@ namespace Penumbra.UI
|
||||||
private OptionGroup? _selectedGroup;
|
private OptionGroup? _selectedGroup;
|
||||||
private int _selectedOptionIndex;
|
private int _selectedOptionIndex;
|
||||||
private Option? _selectedOption;
|
private Option? _selectedOption;
|
||||||
private (string label, string name)[]? _changedItemsList;
|
|
||||||
private string _currentGamePaths = "";
|
private string _currentGamePaths = "";
|
||||||
|
|
||||||
private (FileInfo name, bool selected, uint color, RelPath relName)[]? _fullFilenameList;
|
private (FileInfo name, bool selected, uint color, RelPath relName)[]? _fullFilenameList;
|
||||||
|
|
@ -119,7 +117,6 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
public void ResetState()
|
public void ResetState()
|
||||||
{
|
{
|
||||||
_changedItemsList = null;
|
|
||||||
_fullFilenameList = null;
|
_fullFilenameList = null;
|
||||||
SelectGroup();
|
SelectGroup();
|
||||||
SelectOption();
|
SelectOption();
|
||||||
|
|
@ -186,63 +183,26 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private void DrawChangedItemsTab()
|
private void DrawChangedItemsTab()
|
||||||
{
|
{
|
||||||
if( !_editMode && Meta.ChangedItems.Count == 0 )
|
if( Mod.Data.ChangedItems.Count == 0 || !ImGui.BeginTabItem( LabelChangedItemsTab ) )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags = _editMode
|
|
||||||
? ImGuiInputTextFlags.EnterReturnsTrue
|
|
||||||
: ImGuiInputTextFlags.ReadOnly;
|
|
||||||
|
|
||||||
if( ImGui.BeginTabItem( LabelChangedItemsTab ) )
|
|
||||||
{
|
|
||||||
ImGui.SetNextItemWidth( -1 );
|
|
||||||
var changedItems = false;
|
|
||||||
if( ImGui.BeginListBox( LabelChangedItemsHeader, AutoFillSize ) )
|
if( ImGui.BeginListBox( LabelChangedItemsHeader, AutoFillSize ) )
|
||||||
{
|
{
|
||||||
_changedItemsList ??= Meta.ChangedItems
|
foreach( var item in Mod.Data.ChangedItems )
|
||||||
.Select( ( I, index ) => ( $"{LabelChangedItemIdx}{index}", I ) ).ToArray();
|
|
||||||
|
|
||||||
for( var i = 0; i < Meta.ChangedItems.Count; ++i )
|
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth( -1 );
|
if( ImGui.Selectable( item.Key ) && item.Value is Item it )
|
||||||
if( ImGui.InputText( _changedItemsList[ i ].label, ref _changedItemsList[ i ].name, 128, flags ) )
|
|
||||||
{
|
{
|
||||||
Meta.ChangedItems.RemoveOrChange( _changedItemsList[ i ].name, i );
|
ChatUtil.LinkItem( it );
|
||||||
changedItems = true;
|
|
||||||
_selector.SaveCurrentMod();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var newItem = "";
|
|
||||||
if( _editMode )
|
|
||||||
{
|
|
||||||
ImGui.SetNextItemWidth( -1 );
|
|
||||||
if( ImGui.InputTextWithHint( LabelChangedItemNew, "Enter new changed item...", ref newItem, 128, flags )
|
|
||||||
&& newItem.Length > 0 )
|
|
||||||
{
|
|
||||||
Meta.ChangedItems.Add( newItem );
|
|
||||||
changedItems = true;
|
|
||||||
_selector.SaveCurrentMod();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( changedItems )
|
|
||||||
{
|
|
||||||
_changedItemsList = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndListBox();
|
ImGui.EndListBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_changedItemsList = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawConflictTab()
|
private void DrawConflictTab()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using ImGuiScene;
|
|
||||||
using Penumbra.Importer;
|
using Penumbra.Importer;
|
||||||
using Penumbra.Mod;
|
using Penumbra.Mod;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
|
@ -58,7 +57,10 @@ namespace Penumbra.UI
|
||||||
private const string LabelSelectorList = "##availableModList";
|
private const string LabelSelectorList = "##availableModList";
|
||||||
private const string LabelModFilter = "##ModFilter";
|
private const string LabelModFilter = "##ModFilter";
|
||||||
private const string LabelAddModPopup = "AddMod";
|
private const string LabelAddModPopup = "AddMod";
|
||||||
private const string TooltipModFilter = "Filter mods for those containing the given substring.";
|
|
||||||
|
private const string TooltipModFilter =
|
||||||
|
"Filter mods for those containing the given substring.\nEnter c:[string] to filter for mods changing specific items.\n:Enter a:[string] to filter for mods by specific authors.";
|
||||||
|
|
||||||
private const string TooltipDelete = "Delete the selected mod";
|
private const string TooltipDelete = "Delete the selected mod";
|
||||||
private const string TooltipAdd = "Add an empty mod";
|
private const string TooltipAdd = "Add an empty mod";
|
||||||
private const string DialogDeleteMod = "PenumbraDeleteMod";
|
private const string DialogDeleteMod = "PenumbraDeleteMod";
|
||||||
|
|
@ -82,7 +84,10 @@ namespace Penumbra.UI
|
||||||
public Mod.Mod? Mod { get; private set; }
|
public Mod.Mod? Mod { get; private set; }
|
||||||
private int _index;
|
private int _index;
|
||||||
private int? _deleteIndex;
|
private int? _deleteIndex;
|
||||||
|
private string _modFilterInput = "";
|
||||||
private string _modFilter = "";
|
private string _modFilter = "";
|
||||||
|
private string _modFilterChanges = "";
|
||||||
|
private string _modFilterAuthor = "";
|
||||||
private string[] _modNamesLower;
|
private string[] _modNamesLower;
|
||||||
private ModFilter _stateFilter = UnfilteredStateMods;
|
private ModFilter _stateFilter = UnfilteredStateMods;
|
||||||
|
|
||||||
|
|
@ -191,10 +196,27 @@ namespace Penumbra.UI
|
||||||
private void DrawModsSelectorFilter()
|
private void DrawModsSelectorFilter()
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth( SelectorButtonSizes.X * 2 - 22 );
|
ImGui.SetNextItemWidth( SelectorButtonSizes.X * 2 - 22 );
|
||||||
var tmp = _modFilter;
|
if( ImGui.InputTextWithHint( LabelModFilter, "Filter Mods...", ref _modFilterInput, 256 ) )
|
||||||
if( ImGui.InputTextWithHint( LabelModFilter, "Filter Mods...", ref tmp, 256 ) )
|
|
||||||
{
|
{
|
||||||
_modFilter = tmp.ToLowerInvariant();
|
var lower = _modFilterInput.ToLowerInvariant();
|
||||||
|
if( lower.StartsWith( "c:" ) )
|
||||||
|
{
|
||||||
|
_modFilterChanges = lower.Substring( 2 );
|
||||||
|
_modFilter = string.Empty;
|
||||||
|
_modFilterAuthor = string.Empty;
|
||||||
|
}
|
||||||
|
else if( lower.StartsWith( "a:" ) )
|
||||||
|
{
|
||||||
|
_modFilterAuthor = lower.Substring( 2 );
|
||||||
|
_modFilter = string.Empty;
|
||||||
|
_modFilterChanges = string.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_modFilter = lower;
|
||||||
|
_modFilterAuthor = string.Empty;
|
||||||
|
_modFilterChanges = string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ImGui.IsItemHovered() )
|
if( ImGui.IsItemHovered() )
|
||||||
|
|
@ -304,7 +326,10 @@ namespace Penumbra.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckFilters( Mod.Mod mod, int modIndex )
|
private bool CheckFilters( Mod.Mod mod, int modIndex )
|
||||||
=> ( _modFilter.Length <= 0 || _modNamesLower[ modIndex ].Contains( _modFilter ) )
|
=> ( _modFilter.Length == 0 || _modNamesLower[ modIndex ].Contains( _modFilter ) )
|
||||||
|
&& ( _modFilterAuthor.Length == 0 || mod.Data.Meta.Author.ToLowerInvariant().Contains( _modFilterAuthor ) )
|
||||||
|
&& ( _modFilterChanges.Length == 0
|
||||||
|
|| mod.Data.ChangedItems.Any( s => s.Key.ToLowerInvariant().Contains( _modFilterChanges ) ) )
|
||||||
&& !CheckFlags( mod.Data.Resources.ModFiles.Count, ModFilter.HasNoFiles, ModFilter.HasFiles )
|
&& !CheckFlags( mod.Data.Resources.ModFiles.Count, ModFilter.HasNoFiles, ModFilter.HasFiles )
|
||||||
&& !CheckFlags( mod.Data.Meta.FileSwaps.Count, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps )
|
&& !CheckFlags( mod.Data.Meta.FileSwaps.Count, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps )
|
||||||
&& !CheckFlags( mod.Data.Resources.MetaManipulations.Count, ModFilter.HasNoMetaManipulations, ModFilter.HasMetaManipulations )
|
&& !CheckFlags( mod.Data.Resources.MetaManipulations.Count, ModFilter.HasNoMetaManipulations, ModFilter.HasMetaManipulations )
|
||||||
|
|
|
||||||
41
Penumbra/Util/ChatUtil.cs
Normal file
41
Penumbra/Util/ChatUtil.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Game.Text;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
namespace Penumbra.Util
|
||||||
|
{
|
||||||
|
public static class ChatUtil
|
||||||
|
{
|
||||||
|
private static DalamudPluginInterface? _pi;
|
||||||
|
|
||||||
|
public static void LinkItem( Item item )
|
||||||
|
{
|
||||||
|
_pi ??= Service< DalamudPluginInterface >.Get();
|
||||||
|
|
||||||
|
var payloadList = new List< Payload >
|
||||||
|
{
|
||||||
|
new UIForegroundPayload( _pi.Data, ( ushort )( 0x223 + item.Rarity * 2 ) ),
|
||||||
|
new UIGlowPayload( _pi.Data, ( ushort )( 0x224 + item.Rarity * 2 ) ),
|
||||||
|
new ItemPayload( _pi.Data, item.RowId, false ),
|
||||||
|
new UIForegroundPayload( _pi.Data, 500 ),
|
||||||
|
new UIGlowPayload( _pi.Data, 501 ),
|
||||||
|
new TextPayload( $"{( char )SeIconChar.LinkMarker}" ),
|
||||||
|
new UIForegroundPayload( _pi.Data, 0 ),
|
||||||
|
new UIGlowPayload( _pi.Data, 0 ),
|
||||||
|
new TextPayload( item.Name ),
|
||||||
|
new RawPayload( new byte[] { 0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03 } ),
|
||||||
|
new RawPayload( new byte[] { 0x02, 0x13, 0x02, 0xEC, 0x03 } ),
|
||||||
|
};
|
||||||
|
|
||||||
|
var payload = new SeString( payloadList );
|
||||||
|
|
||||||
|
_pi.Framework.Gui.Chat.PrintChat( new XivChatEntry
|
||||||
|
{
|
||||||
|
MessageBytes = payload.Encode(),
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue