mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Add basic version of item swap, seemingly working for hair, tail and ears.
This commit is contained in:
parent
e534ce37d5
commit
5b3d5d1e67
22 changed files with 1730 additions and 120 deletions
|
|
@ -1,3 +1,5 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -5,6 +7,23 @@ namespace Penumbra.GameData.Data;
|
|||
|
||||
public static partial class GamePaths
|
||||
{
|
||||
private static readonly Regex RaceCodeRegex = new(@"c(?'racecode'\d{4})", RegexOptions.Compiled);
|
||||
|
||||
//[GeneratedRegex(@"c(?'racecode'\d{4})")]
|
||||
public static partial Regex RaceCodeParser();
|
||||
|
||||
public static partial Regex RaceCodeParser()
|
||||
=> RaceCodeRegex;
|
||||
|
||||
public static GenderRace ParseRaceCode(string path)
|
||||
{
|
||||
var match = RaceCodeParser().Match(path);
|
||||
return match.Success
|
||||
? Names.GenderRaceFromCode(match.Groups["racecode"].Value)
|
||||
: GenderRace.Unknown;
|
||||
}
|
||||
|
||||
|
||||
public static partial class Monster
|
||||
{
|
||||
public static partial class Imc
|
||||
|
|
@ -139,7 +158,7 @@ public static partial class GamePaths
|
|||
// public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId equipId, GenderRace raceCode, EquipSlot slot)
|
||||
=> $"chara/equipment/e{equipId.Value:D4}/model/c{(ushort)raceCode:D4}e{equipId.Value:D4}_{slot.ToSuffix()}.mdl";
|
||||
=> $"chara/equipment/e{equipId.Value:D4}/model/c{raceCode.ToRaceCode()}e{equipId.Value:D4}_{slot.ToSuffix()}.mdl";
|
||||
}
|
||||
|
||||
public static partial class Mtrl
|
||||
|
|
@ -148,7 +167,7 @@ public static partial class GamePaths
|
|||
// public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId equipId, GenderRace raceCode, EquipSlot slot, byte variant, string suffix)
|
||||
=> $"{FolderPath(equipId, variant)}/mt_c{(ushort)raceCode:D4}e{equipId.Value:D4}_{slot.ToSuffix()}_{suffix}.mtrl";
|
||||
=> $"{FolderPath(equipId, variant)}/mt_c{raceCode.ToRaceCode()}e{equipId.Value:D4}_{slot.ToSuffix()}_{suffix}.mtrl";
|
||||
|
||||
public static string FolderPath(SetId equipId, byte variant)
|
||||
=> $"chara/equipment/e{equipId.Value:D4}/material/v{variant:D4}";
|
||||
|
|
@ -160,7 +179,25 @@ public static partial class GamePaths
|
|||
// public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId equipId, GenderRace raceCode, EquipSlot slot, byte variant, char suffix1, char suffix2 = '\0')
|
||||
=> $"chara/equipment/e{equipId.Value:D4}/texture/v{variant:D2}_c{(ushort)raceCode:D4}e{equipId.Value:D4}_{slot.ToSuffix()}{(suffix2 != '\0' ? $"_{suffix2}" : string.Empty)}_{suffix1}.tex";
|
||||
=> $"chara/equipment/e{equipId.Value:D4}/texture/v{variant:D2}_c{raceCode.ToRaceCode()}e{equipId.Value:D4}_{slot.ToSuffix()}{(suffix2 != '\0' ? $"_{suffix2}" : string.Empty)}_{suffix1}.tex";
|
||||
}
|
||||
|
||||
public static partial class Avfx
|
||||
{
|
||||
//[GeneratedRegex(@"chara/equipment/e(?'id'\d{4})/vfx/eff/ve(?'variant'\d{4})\.avfx")]
|
||||
//public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId equipId, byte effectId)
|
||||
=> $"chara/equipment/e{equipId.Value:D4}/vfx/eff/ve{effectId:D4}.avfx";
|
||||
}
|
||||
|
||||
public static partial class Decal
|
||||
{
|
||||
//[GeneratedRegex(@"chara/common/texture/decal_equip/-decal_(?'decalId'\d{3})\.tex")]
|
||||
//public static partial Regex Regex();
|
||||
|
||||
public static string Path(byte decalId)
|
||||
=> $"chara/common/texture/decal_equip/-decal_{decalId:D3}.tex";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +218,7 @@ public static partial class GamePaths
|
|||
// public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId accessoryId, GenderRace raceCode, EquipSlot slot)
|
||||
=> $"chara/accessory/a{accessoryId.Value:D4}/model/c{(ushort)raceCode:D4}a{accessoryId.Value:D4}_{slot.ToSuffix()}.mdl";
|
||||
=> $"chara/accessory/a{accessoryId.Value:D4}/model/c{raceCode.ToRaceCode()}a{accessoryId.Value:D4}_{slot.ToSuffix()}.mdl";
|
||||
}
|
||||
|
||||
public static partial class Mtrl
|
||||
|
|
@ -190,7 +227,7 @@ public static partial class GamePaths
|
|||
// public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId accessoryId, GenderRace raceCode, EquipSlot slot, byte variant, string suffix)
|
||||
=> $"{FolderPath(accessoryId, variant)}/c{(ushort)raceCode:D4}a{accessoryId.Value:D4}_{slot.ToSuffix()}_{suffix}.mtrl";
|
||||
=> $"{FolderPath(accessoryId, variant)}/c{raceCode.ToRaceCode()}a{accessoryId.Value:D4}_{slot.ToSuffix()}_{suffix}.mtrl";
|
||||
|
||||
public static string FolderPath(SetId accessoryId, byte variant)
|
||||
=> $"chara/accessory/a{accessoryId.Value:D4}/material/v{variant:D4}";
|
||||
|
|
@ -202,7 +239,7 @@ public static partial class GamePaths
|
|||
// public static partial Regex Regex();
|
||||
|
||||
public static string Path(SetId accessoryId, GenderRace raceCode, EquipSlot slot, byte variant, char suffix1, char suffix2 = '\0')
|
||||
=> $"chara/accessory/a{accessoryId.Value:D4}/texture/v{variant:D2}_c{(ushort)raceCode:D4}a{accessoryId.Value:D4}_{slot.ToSuffix()}{(suffix2 != '\0' ? $"_{suffix2}" : string.Empty)}_{suffix1}.tex";
|
||||
=> $"chara/accessory/a{accessoryId.Value:D4}/texture/v{variant:D2}_c{raceCode.ToRaceCode()}a{accessoryId.Value:D4}_{slot.ToSuffix()}{(suffix2 != '\0' ? $"_{suffix2}" : string.Empty)}_{suffix1}.tex";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +251,19 @@ public static partial class GamePaths
|
|||
// public static partial Regex Regex();
|
||||
|
||||
public static string Path(GenderRace raceCode, BodySlot slot, SetId slotId, CustomizationType type)
|
||||
=> $"chara/human/c{(ushort)raceCode:D4}/obj/{slot.ToSuffix()}/{slot.ToAbbreviation()}{slotId.Value:D4}/model/c{(ushort)raceCode:D4}{slot.ToAbbreviation()}{slotId.Value:D4}_{type.ToSuffix()}.mdl";
|
||||
=> $"chara/human/c{raceCode.ToRaceCode()}/obj/{slot.ToSuffix()}/{slot.ToAbbreviation()}{slotId.Value:D4}/model/c{raceCode.ToRaceCode()}{slot.ToAbbreviation()}{slotId.Value:D4}_{type.ToSuffix()}.mdl";
|
||||
}
|
||||
|
||||
public static partial class Phyb
|
||||
{
|
||||
public static string Path(GenderRace raceCode, BodySlot slot, SetId slotId)
|
||||
=> $"chara/human/c{raceCode.ToRaceCode()}/skeleton/{slot.ToSuffix()}/{slot.ToAbbreviation()}{slotId.Value:D4}/phy_c{raceCode.ToRaceCode()}{slot.ToAbbreviation()}{slotId.Value:D4}.phyb";
|
||||
}
|
||||
|
||||
public static partial class Sklb
|
||||
{
|
||||
public static string Path(GenderRace raceCode, BodySlot slot, SetId slotId)
|
||||
=> $"chara/human/c{raceCode.ToRaceCode()}/skeleton/{slot.ToSuffix()}/{slot.ToAbbreviation()}{slotId.Value:D4}/skl_c{raceCode.ToRaceCode()}{slot.ToAbbreviation()}{slotId.Value:D4}.sklb";
|
||||
}
|
||||
|
||||
public static partial class Mtrl
|
||||
|
|
@ -222,11 +271,52 @@ public static partial class GamePaths
|
|||
// [GeneratedRegex(@"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")]
|
||||
// public static partial Regex Regex();
|
||||
|
||||
public static string Path(GenderRace raceCode, BodySlot slot, SetId slotId, string suffix,
|
||||
CustomizationType type = CustomizationType.Unknown, byte variant = byte.MaxValue)
|
||||
=> $"chara/human/c{(ushort)raceCode:D4}/obj/{slot.ToSuffix()}/{slot.ToAbbreviation()}{slotId.Value:D4}/material/"
|
||||
+ (variant != byte.MaxValue ? $"v{variant:D4}/" : string.Empty)
|
||||
+ $"mt_c{(ushort)raceCode:D4}{slot.ToAbbreviation()}{slotId.Value:D4}{(type != CustomizationType.Unknown ? $"_{type.ToSuffix()}" : string.Empty)}_{suffix}.mtrl";
|
||||
public static string FolderPath(GenderRace raceCode, BodySlot slot, SetId slotId, byte variant = byte.MaxValue)
|
||||
=> $"chara/human/c{raceCode.ToRaceCode()}/obj/{slot.ToSuffix()}/{slot.ToAbbreviation()}{slotId.Value:D4}/material{(variant != byte.MaxValue ? $"/v{variant:D4}" : string.Empty)}";
|
||||
|
||||
public static string HairPath(GenderRace raceCode, SetId slotId, string fileName, out GenderRace actualGr)
|
||||
{
|
||||
actualGr = MaterialHandling.GetGameGenderRace(raceCode, slotId);
|
||||
var folder = FolderPath(actualGr, BodySlot.Hair, slotId, 1);
|
||||
return actualGr == raceCode
|
||||
? $"{folder}{fileName}"
|
||||
: $"{folder}/mt_c{actualGr.ToRaceCode()}{fileName[9..]}";
|
||||
}
|
||||
|
||||
public static string TailPath(GenderRace raceCode, SetId slotId, string fileName, byte variant, out SetId actualSlotId)
|
||||
{
|
||||
switch (raceCode)
|
||||
{
|
||||
case GenderRace.HrothgarMale:
|
||||
case GenderRace.HrothgarFemale:
|
||||
case GenderRace.HrothgarMaleNpc:
|
||||
case GenderRace.HrothgarFemaleNpc:
|
||||
var folder = FolderPath(raceCode, BodySlot.Tail, 1, variant == byte.MaxValue ? (byte)1 : variant);
|
||||
actualSlotId = 1;
|
||||
return $"{folder}{fileName}";
|
||||
default:
|
||||
actualSlotId = slotId;
|
||||
return $"{FolderPath(raceCode, BodySlot.Tail, slotId, variant)}{fileName}";
|
||||
}
|
||||
}
|
||||
|
||||
public static string Path(GenderRace raceCode, BodySlot slot, SetId slotId, string fileName,
|
||||
out GenderRace actualGr, out SetId actualSlotId, byte variant = byte.MaxValue)
|
||||
{
|
||||
switch (slot)
|
||||
{
|
||||
case BodySlot.Hair:
|
||||
actualSlotId = slotId;
|
||||
return HairPath(raceCode, slotId, fileName, out actualGr);
|
||||
case BodySlot.Tail:
|
||||
actualGr = raceCode;
|
||||
return TailPath(raceCode, slotId, fileName, variant, out actualSlotId);
|
||||
default:
|
||||
actualSlotId = slotId;
|
||||
actualGr = raceCode;
|
||||
return $"{FolderPath(raceCode, slot, slotId, variant)}{fileName}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Tex
|
||||
|
|
@ -236,10 +326,10 @@ public static partial class GamePaths
|
|||
|
||||
public static string Path(GenderRace raceCode, BodySlot slot, SetId slotId, char suffix1, bool minus = false,
|
||||
CustomizationType type = CustomizationType.Unknown, byte variant = byte.MaxValue, char suffix2 = '\0')
|
||||
=> $"chara/human/c{(ushort)raceCode:D4}/obj/{slot.ToSuffix()}/{slot.ToAbbreviation()}{slotId.Value:D4}/texture/"
|
||||
=> $"chara/human/c{raceCode.ToRaceCode()}/obj/{slot.ToSuffix()}/{slot.ToAbbreviation()}{slotId.Value:D4}/texture/"
|
||||
+ (minus ? "--" : string.Empty)
|
||||
+ (variant != byte.MaxValue ? $"v{variant:D2}_" : string.Empty)
|
||||
+ $"c{(ushort)raceCode:D4}{slot.ToAbbreviation()}{slotId.Value:D4}{(type != CustomizationType.Unknown ? $"_{type.ToSuffix()}" : string.Empty)}{(suffix2 != '\0' ? $"_{suffix2}" : string.Empty)}_{suffix1}.tex";
|
||||
+ $"c{raceCode.ToRaceCode()}{slot.ToAbbreviation()}{slotId.Value:D4}{(type != CustomizationType.Unknown ? $"_{type.ToSuffix()}" : string.Empty)}{(suffix2 != '\0' ? $"_{suffix2}" : string.Empty)}_{suffix1}.tex";
|
||||
|
||||
|
||||
// [GeneratedRegex(@"chara/common/texture/(?'catchlight'catchlight)(.*)\.tex")]
|
||||
|
|
|
|||
31
Penumbra.GameData/Data/MaterialHandling.cs
Normal file
31
Penumbra.GameData/Data/MaterialHandling.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.GameData.Data;
|
||||
|
||||
public static class MaterialHandling
|
||||
{
|
||||
public static GenderRace GetGameGenderRace(GenderRace actualGr, SetId hairId)
|
||||
{
|
||||
// Hrothgar do not share hairstyles.
|
||||
if (actualGr is GenderRace.HrothgarFemale or GenderRace.HrothgarMale)
|
||||
return actualGr;
|
||||
|
||||
// Some hairstyles are miqo'te specific but otherwise shared.
|
||||
if (hairId.Value is >= 101 and <= 115)
|
||||
{
|
||||
if (actualGr is GenderRace.MiqoteFemale or GenderRace.MiqoteMale)
|
||||
return actualGr;
|
||||
|
||||
return actualGr.Split().Item1 == Gender.Female ? GenderRace.MidlanderFemale : GenderRace.MidlanderMale;
|
||||
}
|
||||
|
||||
// All hairstyles above 116 are shared except for Hrothgar
|
||||
if (hairId.Value is >= 116 and <= 200)
|
||||
{
|
||||
return actualGr.Split().Item1 == Gender.Female ? GenderRace.MidlanderFemale : GenderRace.MidlanderMale;
|
||||
}
|
||||
|
||||
return actualGr;
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,17 @@ public static class BodySlotEnumExtension
|
|||
BodySlot.Zear => 'z',
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
|
||||
public static CustomizationType ToCustomizationType(this BodySlot value)
|
||||
=> value switch
|
||||
{
|
||||
BodySlot.Hair => CustomizationType.Hair,
|
||||
BodySlot.Face => CustomizationType.Face,
|
||||
BodySlot.Tail => CustomizationType.Tail,
|
||||
BodySlot.Body => CustomizationType.Body,
|
||||
BodySlot.Zear => CustomizationType.Zear,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(value), value, null)
|
||||
};
|
||||
}
|
||||
|
||||
public static partial class Names
|
||||
|
|
|
|||
|
|
@ -328,7 +328,7 @@ public static class RaceEnumExtensions
|
|||
VieraFemaleNpc => "1804",
|
||||
UnknownMaleNpc => "9104",
|
||||
UnknownFemaleNpc => "9204",
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -427,7 +427,7 @@ public static partial class Names
|
|||
"1804" => VieraFemaleNpc,
|
||||
"9104" => UnknownMaleNpc,
|
||||
"9204" => UnknownFemaleNpc,
|
||||
_ => throw new KeyNotFoundException(),
|
||||
_ => Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
namespace Penumbra.GameData.Files;
|
||||
|
||||
public interface IWritable
|
||||
{
|
||||
{
|
||||
public bool Valid { get; }
|
||||
public byte[] Write();
|
||||
}
|
||||
|
|
@ -83,7 +83,9 @@ public partial class MdlFile : IWritable
|
|||
public Shape[] Shapes;
|
||||
|
||||
// Raw, unparsed data.
|
||||
public byte[] RemainingData;
|
||||
public byte[] RemainingData;
|
||||
|
||||
public bool Valid { get; }
|
||||
|
||||
public MdlFile(byte[] data)
|
||||
{
|
||||
|
|
@ -180,6 +182,7 @@ public partial class MdlFile : IWritable
|
|||
BoneBoundingBoxes[i] = MdlStructs.BoundingBoxStruct.Read(r);
|
||||
|
||||
RemainingData = r.ReadBytes((int)(r.BaseStream.Length - r.BaseStream.Position));
|
||||
Valid = true;
|
||||
}
|
||||
|
||||
private MdlStructs.ModelFileHeader LoadModelFileHeader(LuminaBinaryReader r)
|
||||
|
|
|
|||
|
|
@ -231,6 +231,9 @@ public partial class MtrlFile : IWritable
|
|||
{
|
||||
public string Path;
|
||||
public ushort Flags;
|
||||
|
||||
public bool DX11
|
||||
=> (Flags & 0x8000) != 0;
|
||||
}
|
||||
|
||||
public struct Constant
|
||||
|
|
@ -251,6 +254,7 @@ public partial class MtrlFile : IWritable
|
|||
|
||||
|
||||
public readonly uint Version;
|
||||
public bool Valid { get; }
|
||||
|
||||
public Texture[] Textures;
|
||||
public UvSet[] UvSets;
|
||||
|
|
@ -368,6 +372,7 @@ public partial class MtrlFile : IWritable
|
|||
ShaderPackage.Constants = r.ReadStructuresAsArray<Constant>(constantCount);
|
||||
ShaderPackage.Samplers = r.ReadStructuresAsArray<Sampler>(samplerCount);
|
||||
ShaderPackage.ShaderValues = r.ReadStructuresAsArray<float>(shaderValueListSize / 4);
|
||||
Valid = true;
|
||||
}
|
||||
|
||||
private static Texture[] ReadTextureOffsets(BinaryReader r, int count, out ushort[] offsets)
|
||||
|
|
|
|||
|
|
@ -87,39 +87,42 @@ public enum EqpEntry : ulong
|
|||
public static class Eqp
|
||||
{
|
||||
// cf. Client::Graphics::Scene::CharacterUtility.GetSlotEqpFlags
|
||||
public const EqpEntry DefaultEntry = ( EqpEntry )0x3fe00070603f00;
|
||||
public const EqpEntry DefaultEntry = (EqpEntry)0x3fe00070603f00;
|
||||
|
||||
public static (int, int) BytesAndOffset( EquipSlot slot )
|
||||
public static (int, int) BytesAndOffset(EquipSlot slot)
|
||||
{
|
||||
return slot switch
|
||||
{
|
||||
EquipSlot.Body => ( 2, 0 ),
|
||||
EquipSlot.Legs => ( 1, 2 ),
|
||||
EquipSlot.Hands => ( 1, 3 ),
|
||||
EquipSlot.Feet => ( 1, 4 ),
|
||||
EquipSlot.Head => ( 3, 5 ),
|
||||
EquipSlot.Body => (2, 0),
|
||||
EquipSlot.Legs => (1, 2),
|
||||
EquipSlot.Hands => (1, 3),
|
||||
EquipSlot.Feet => (1, 4),
|
||||
EquipSlot.Head => (3, 5),
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static EqpEntry FromSlotAndBytes( EquipSlot slot, byte[] value )
|
||||
public static EqpEntry ShiftAndMask(this EqpEntry entry, EquipSlot slot)
|
||||
{
|
||||
var (_, offset) = BytesAndOffset(slot);
|
||||
var mask = Mask(slot);
|
||||
return (EqpEntry)((ulong)(entry & mask) >> (offset * 8));
|
||||
}
|
||||
|
||||
public static EqpEntry FromSlotAndBytes(EquipSlot slot, byte[] value)
|
||||
{
|
||||
EqpEntry ret = 0;
|
||||
var (bytes, offset) = BytesAndOffset( slot );
|
||||
if( bytes != value.Length )
|
||||
{
|
||||
var (bytes, offset) = BytesAndOffset(slot);
|
||||
if (bytes != value.Length)
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
for( var i = 0; i < bytes; ++i )
|
||||
{
|
||||
ret |= ( EqpEntry )( ( ulong )value[ i ] << ( ( offset + i ) * 8 ) );
|
||||
}
|
||||
for (var i = 0; i < bytes; ++i)
|
||||
ret |= (EqpEntry)((ulong)value[i] << ((offset + i) * 8));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static EqpEntry Mask( EquipSlot slot )
|
||||
public static EqpEntry Mask(EquipSlot slot)
|
||||
{
|
||||
return slot switch
|
||||
{
|
||||
|
|
@ -132,7 +135,7 @@ public static class Eqp
|
|||
};
|
||||
}
|
||||
|
||||
public static EquipSlot ToEquipSlot( this EqpEntry entry )
|
||||
public static EquipSlot ToEquipSlot(this EqpEntry entry)
|
||||
{
|
||||
return entry switch
|
||||
{
|
||||
|
|
@ -211,7 +214,7 @@ public static class Eqp
|
|||
};
|
||||
}
|
||||
|
||||
public static string ToLocalName( this EqpEntry entry )
|
||||
public static string ToLocalName(this EqpEntry entry)
|
||||
{
|
||||
return entry switch
|
||||
{
|
||||
|
|
@ -289,25 +292,25 @@ public static class Eqp
|
|||
};
|
||||
}
|
||||
|
||||
private static EqpEntry[] GetEntriesForSlot( EquipSlot slot )
|
||||
private static EqpEntry[] GetEntriesForSlot(EquipSlot slot)
|
||||
{
|
||||
return ( ( EqpEntry[] )Enum.GetValues( typeof( EqpEntry ) ) )
|
||||
.Where( e => e.ToEquipSlot() == slot )
|
||||
.ToArray();
|
||||
return ((EqpEntry[])Enum.GetValues(typeof(EqpEntry)))
|
||||
.Where(e => e.ToEquipSlot() == slot)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static readonly EqpEntry[] EqpAttributesBody = GetEntriesForSlot( EquipSlot.Body );
|
||||
public static readonly EqpEntry[] EqpAttributesLegs = GetEntriesForSlot( EquipSlot.Legs );
|
||||
public static readonly EqpEntry[] EqpAttributesHands = GetEntriesForSlot( EquipSlot.Hands );
|
||||
public static readonly EqpEntry[] EqpAttributesFeet = GetEntriesForSlot( EquipSlot.Feet );
|
||||
public static readonly EqpEntry[] EqpAttributesHead = GetEntriesForSlot( EquipSlot.Head );
|
||||
public static readonly EqpEntry[] EqpAttributesBody = GetEntriesForSlot(EquipSlot.Body);
|
||||
public static readonly EqpEntry[] EqpAttributesLegs = GetEntriesForSlot(EquipSlot.Legs);
|
||||
public static readonly EqpEntry[] EqpAttributesHands = GetEntriesForSlot(EquipSlot.Hands);
|
||||
public static readonly EqpEntry[] EqpAttributesFeet = GetEntriesForSlot(EquipSlot.Feet);
|
||||
public static readonly EqpEntry[] EqpAttributesHead = GetEntriesForSlot(EquipSlot.Head);
|
||||
|
||||
public static readonly IReadOnlyDictionary< EquipSlot, EqpEntry[] > EqpAttributes = new Dictionary< EquipSlot, EqpEntry[] >()
|
||||
public static readonly IReadOnlyDictionary<EquipSlot, EqpEntry[]> EqpAttributes = new Dictionary<EquipSlot, EqpEntry[]>()
|
||||
{
|
||||
[ EquipSlot.Body ] = EqpAttributesBody,
|
||||
[ EquipSlot.Legs ] = EqpAttributesLegs,
|
||||
[ EquipSlot.Hands ] = EqpAttributesHands,
|
||||
[ EquipSlot.Feet ] = EqpAttributesFeet,
|
||||
[ EquipSlot.Head ] = EqpAttributesHead,
|
||||
[EquipSlot.Body] = EqpAttributesBody,
|
||||
[EquipSlot.Legs] = EqpAttributesLegs,
|
||||
[EquipSlot.Hands] = EqpAttributesHands,
|
||||
[EquipSlot.Feet] = EqpAttributesFeet,
|
||||
[EquipSlot.Head] = EqpAttributesHead,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue