mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-24 01:19:22 +01:00
Introduce Identifiers and strong entry types for each meta manipulation and use them in the manipulations.
This commit is contained in:
parent
ceed8531af
commit
2e9f184454
27 changed files with 533 additions and 163 deletions
|
|
@ -48,33 +48,33 @@ public struct EstCache : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager, EstManipulation.EstType type)
|
public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager, EstType type)
|
||||||
{
|
{
|
||||||
var (file, idx) = type switch
|
var (file, idx) = type switch
|
||||||
{
|
{
|
||||||
EstManipulation.EstType.Face => (_estFaceFile, MetaIndex.FaceEst),
|
EstType.Face => (_estFaceFile, MetaIndex.FaceEst),
|
||||||
EstManipulation.EstType.Hair => (_estHairFile, MetaIndex.HairEst),
|
EstType.Hair => (_estHairFile, MetaIndex.HairEst),
|
||||||
EstManipulation.EstType.Body => (_estBodyFile, MetaIndex.BodyEst),
|
EstType.Body => (_estBodyFile, MetaIndex.BodyEst),
|
||||||
EstManipulation.EstType.Head => (_estHeadFile, MetaIndex.HeadEst),
|
EstType.Head => (_estHeadFile, MetaIndex.HeadEst),
|
||||||
_ => (null, 0),
|
_ => (null, 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
return manager.TemporarilySetFile(file, idx);
|
return manager.TemporarilySetFile(file, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly EstFile? GetEstFile(EstManipulation.EstType type)
|
private readonly EstFile? GetEstFile(EstType type)
|
||||||
{
|
{
|
||||||
return type switch
|
return type switch
|
||||||
{
|
{
|
||||||
EstManipulation.EstType.Face => _estFaceFile,
|
EstType.Face => _estFaceFile,
|
||||||
EstManipulation.EstType.Hair => _estHairFile,
|
EstType.Hair => _estHairFile,
|
||||||
EstManipulation.EstType.Body => _estBodyFile,
|
EstType.Body => _estBodyFile,
|
||||||
EstManipulation.EstType.Head => _estHeadFile,
|
EstType.Head => _estHeadFile,
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ushort GetEstEntry(MetaFileManager manager, EstManipulation.EstType type, GenderRace genderRace, PrimaryId primaryId)
|
internal EstEntry GetEstEntry(MetaFileManager manager, EstType type, GenderRace genderRace, PrimaryId primaryId)
|
||||||
{
|
{
|
||||||
var file = GetEstFile(type);
|
var file = GetEstFile(type);
|
||||||
return file != null
|
return file != null
|
||||||
|
|
@ -96,10 +96,10 @@ public struct EstCache : IDisposable
|
||||||
_estManipulations.AddOrReplace(m);
|
_estManipulations.AddOrReplace(m);
|
||||||
var file = m.Slot switch
|
var file = m.Slot switch
|
||||||
{
|
{
|
||||||
EstManipulation.EstType.Hair => _estHairFile ??= new EstFile(manager, EstManipulation.EstType.Hair),
|
EstType.Hair => _estHairFile ??= new EstFile(manager, EstType.Hair),
|
||||||
EstManipulation.EstType.Face => _estFaceFile ??= new EstFile(manager, EstManipulation.EstType.Face),
|
EstType.Face => _estFaceFile ??= new EstFile(manager, EstType.Face),
|
||||||
EstManipulation.EstType.Body => _estBodyFile ??= new EstFile(manager, EstManipulation.EstType.Body),
|
EstType.Body => _estBodyFile ??= new EstFile(manager, EstType.Body),
|
||||||
EstManipulation.EstType.Head => _estHeadFile ??= new EstFile(manager, EstManipulation.EstType.Head),
|
EstType.Head => _estHeadFile ??= new EstFile(manager, EstType.Head),
|
||||||
_ => throw new ArgumentOutOfRangeException(),
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
};
|
};
|
||||||
return m.Apply(file);
|
return m.Apply(file);
|
||||||
|
|
@ -114,10 +114,10 @@ public struct EstCache : IDisposable
|
||||||
var manip = new EstManipulation(m.Gender, m.Race, m.Slot, m.SetId, def);
|
var manip = new EstManipulation(m.Gender, m.Race, m.Slot, m.SetId, def);
|
||||||
var file = m.Slot switch
|
var file = m.Slot switch
|
||||||
{
|
{
|
||||||
EstManipulation.EstType.Hair => _estHairFile!,
|
EstType.Hair => _estHairFile!,
|
||||||
EstManipulation.EstType.Face => _estFaceFile!,
|
EstType.Face => _estFaceFile!,
|
||||||
EstManipulation.EstType.Body => _estBodyFile!,
|
EstType.Body => _estBodyFile!,
|
||||||
EstManipulation.EstType.Head => _estHeadFile!,
|
EstType.Head => _estHeadFile!,
|
||||||
_ => throw new ArgumentOutOfRangeException(),
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
};
|
};
|
||||||
return manip.Apply(file);
|
return manip.Apply(file);
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
|
||||||
public MetaList.MetaReverter TemporarilySetCmpFile()
|
public MetaList.MetaReverter TemporarilySetCmpFile()
|
||||||
=> _cmpCache.TemporarilySetFiles(_manager);
|
=> _cmpCache.TemporarilySetFiles(_manager);
|
||||||
|
|
||||||
public MetaList.MetaReverter TemporarilySetEstFile(EstManipulation.EstType type)
|
public MetaList.MetaReverter TemporarilySetEstFile(EstType type)
|
||||||
=> _estCache.TemporarilySetFiles(_manager, type);
|
=> _estCache.TemporarilySetFiles(_manager, type);
|
||||||
|
|
||||||
public unsafe EqpEntry ApplyGlobalEqp(EqpEntry baseEntry, CharacterArmor* armor)
|
public unsafe EqpEntry ApplyGlobalEqp(EqpEntry baseEntry, CharacterArmor* armor)
|
||||||
|
|
@ -208,7 +208,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
|
||||||
return Meta.Files.ExpandedEqdpFile.GetDefault(_manager, race, accessory, primaryId);
|
return Meta.Files.ExpandedEqdpFile.GetDefault(_manager, race, accessory, primaryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ushort GetEstEntry(EstManipulation.EstType type, GenderRace genderRace, PrimaryId primaryId)
|
internal EstEntry GetEstEntry(EstType type, GenderRace genderRace, PrimaryId primaryId)
|
||||||
=> _estCache.GetEstEntry(_manager, type, genderRace, primaryId);
|
=> _estCache.GetEstEntry(_manager, type, genderRace, primaryId);
|
||||||
|
|
||||||
/// <summary> Use this when CharacterUtility becomes ready. </summary>
|
/// <summary> Use this when CharacterUtility becomes ready. </summary>
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ public partial class ModCollection
|
||||||
=> _cache?.Meta.TemporarilySetCmpFile()
|
=> _cache?.Meta.TemporarilySetCmpFile()
|
||||||
?? utility.TemporarilyResetResource(MetaIndex.HumanCmp);
|
?? utility.TemporarilyResetResource(MetaIndex.HumanCmp);
|
||||||
|
|
||||||
public MetaList.MetaReverter TemporarilySetEstFile(CharacterUtility utility, EstManipulation.EstType type)
|
public MetaList.MetaReverter TemporarilySetEstFile(CharacterUtility utility, EstType type)
|
||||||
=> _cache?.Meta.TemporarilySetEstFile(type)
|
=> _cache?.Meta.TemporarilySetEstFile(type)
|
||||||
?? utility.TemporarilyResetResource((MetaIndex)type);
|
?? utility.TemporarilyResetResource((MetaIndex)type);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,16 +63,16 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
|
||||||
return info.ObjectType switch
|
return info.ObjectType switch
|
||||||
{
|
{
|
||||||
ObjectType.Equipment when info.EquipSlot.ToSlot() is EquipSlot.Body
|
ObjectType.Equipment when info.EquipSlot.ToSlot() is EquipSlot.Body
|
||||||
=> [baseSkeleton, ..ResolveEstSkeleton(EstManipulation.EstType.Body, info, estManipulations)],
|
=> [baseSkeleton, ..ResolveEstSkeleton(EstType.Body, info, estManipulations)],
|
||||||
ObjectType.Equipment when info.EquipSlot.ToSlot() is EquipSlot.Head
|
ObjectType.Equipment when info.EquipSlot.ToSlot() is EquipSlot.Head
|
||||||
=> [baseSkeleton, ..ResolveEstSkeleton(EstManipulation.EstType.Head, info, estManipulations)],
|
=> [baseSkeleton, ..ResolveEstSkeleton(EstType.Head, info, estManipulations)],
|
||||||
ObjectType.Equipment => [baseSkeleton],
|
ObjectType.Equipment => [baseSkeleton],
|
||||||
ObjectType.Accessory => [baseSkeleton],
|
ObjectType.Accessory => [baseSkeleton],
|
||||||
ObjectType.Character when info.BodySlot is BodySlot.Body or BodySlot.Tail => [baseSkeleton],
|
ObjectType.Character when info.BodySlot is BodySlot.Body or BodySlot.Tail => [baseSkeleton],
|
||||||
ObjectType.Character when info.BodySlot is BodySlot.Hair
|
ObjectType.Character when info.BodySlot is BodySlot.Hair
|
||||||
=> [baseSkeleton, ..ResolveEstSkeleton(EstManipulation.EstType.Hair, info, estManipulations)],
|
=> [baseSkeleton, ..ResolveEstSkeleton(EstType.Hair, info, estManipulations)],
|
||||||
ObjectType.Character when info.BodySlot is BodySlot.Face or BodySlot.Ear
|
ObjectType.Character when info.BodySlot is BodySlot.Face or BodySlot.Ear
|
||||||
=> [baseSkeleton, ..ResolveEstSkeleton(EstManipulation.EstType.Face, info, estManipulations)],
|
=> [baseSkeleton, ..ResolveEstSkeleton(EstType.Face, info, estManipulations)],
|
||||||
ObjectType.Character => throw new Exception($"Currently unsupported human model type \"{info.BodySlot}\"."),
|
ObjectType.Character => throw new Exception($"Currently unsupported human model type \"{info.BodySlot}\"."),
|
||||||
ObjectType.DemiHuman => [GamePaths.DemiHuman.Sklb.Path(info.PrimaryId)],
|
ObjectType.DemiHuman => [GamePaths.DemiHuman.Sklb.Path(info.PrimaryId)],
|
||||||
ObjectType.Monster => [GamePaths.Monster.Sklb.Path(info.PrimaryId)],
|
ObjectType.Monster => [GamePaths.Monster.Sklb.Path(info.PrimaryId)],
|
||||||
|
|
@ -81,7 +81,7 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private string[] ResolveEstSkeleton(EstManipulation.EstType type, GameObjectInfo info, EstManipulation[] estManipulations)
|
private string[] ResolveEstSkeleton(EstType type, GameObjectInfo info, EstManipulation[] estManipulations)
|
||||||
{
|
{
|
||||||
// Try to find an EST entry from the manipulations provided.
|
// Try to find an EST entry from the manipulations provided.
|
||||||
var (gender, race) = info.GenderRace.Split();
|
var (gender, race) = info.GenderRace.Split();
|
||||||
|
|
@ -96,13 +96,13 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
|
||||||
// Try to use an entry from provided manipulations, falling back to the current collection.
|
// Try to use an entry from provided manipulations, falling back to the current collection.
|
||||||
var targetId = modEst?.Entry
|
var targetId = modEst?.Entry
|
||||||
?? collections.Current.MetaCache?.GetEstEntry(type, info.GenderRace, info.PrimaryId)
|
?? collections.Current.MetaCache?.GetEstEntry(type, info.GenderRace, info.PrimaryId)
|
||||||
?? 0;
|
?? EstEntry.Zero;
|
||||||
|
|
||||||
// If there's no entries, we can assume that there's no additional skeleton.
|
// If there's no entries, we can assume that there's no additional skeleton.
|
||||||
if (targetId == 0)
|
if (targetId == EstEntry.Zero)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
return [GamePaths.Skeleton.Sklb.Path(info.GenderRace, EstManipulation.ToName(type), targetId)];
|
return [GamePaths.Skeleton.Sklb.Path(info.GenderRace, EstManipulation.ToName(type), targetId.AsId)];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Try to resolve the absolute path to a .mtrl from the potentially-partial path provided by a model. </summary>
|
/// <summary> Try to resolve the absolute path to a .mtrl from the potentially-partial path provided by a model. </summary>
|
||||||
|
|
|
||||||
|
|
@ -75,14 +75,14 @@ public partial class TexToolsMeta
|
||||||
{
|
{
|
||||||
var gr = (GenderRace)reader.ReadUInt16();
|
var gr = (GenderRace)reader.ReadUInt16();
|
||||||
var id = reader.ReadUInt16();
|
var id = reader.ReadUInt16();
|
||||||
var value = reader.ReadUInt16();
|
var value = new EstEntry(reader.ReadUInt16());
|
||||||
var type = (metaFileInfo.SecondaryType, metaFileInfo.EquipSlot) switch
|
var type = (metaFileInfo.SecondaryType, metaFileInfo.EquipSlot) switch
|
||||||
{
|
{
|
||||||
(BodySlot.Face, _) => EstManipulation.EstType.Face,
|
(BodySlot.Face, _) => EstType.Face,
|
||||||
(BodySlot.Hair, _) => EstManipulation.EstType.Hair,
|
(BodySlot.Hair, _) => EstType.Hair,
|
||||||
(_, EquipSlot.Head) => EstManipulation.EstType.Head,
|
(_, EquipSlot.Head) => EstType.Head,
|
||||||
(_, EquipSlot.Body) => EstManipulation.EstType.Body,
|
(_, EquipSlot.Body) => EstType.Body,
|
||||||
_ => (EstManipulation.EstType)0,
|
_ => (EstType)0,
|
||||||
};
|
};
|
||||||
if (!gr.IsValid() || type == 0)
|
if (!gr.IsValid() || type == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ public partial class TexToolsMeta
|
||||||
foreach (var attribute in attributes)
|
foreach (var attribute in attributes)
|
||||||
{
|
{
|
||||||
var value = list.TryGetValue(attribute, out var tmp) ? tmp.Entry : CmpFile.GetDefault(manager, race, attribute);
|
var value = list.TryGetValue(attribute, out var tmp) ? tmp.Entry : CmpFile.GetDefault(manager, race, attribute);
|
||||||
b.Write(value);
|
b.Write(value.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,7 +176,7 @@ public partial class TexToolsMeta
|
||||||
{
|
{
|
||||||
b.Write((ushort)Names.CombinedRace(manip.Est.Gender, manip.Est.Race));
|
b.Write((ushort)Names.CombinedRace(manip.Est.Gender, manip.Est.Race));
|
||||||
b.Write(manip.Est.SetId.Id);
|
b.Write(manip.Est.SetId.Id);
|
||||||
b.Write(manip.Est.Entry);
|
b.Write(manip.Est.Entry.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -239,10 +239,10 @@ public partial class TexToolsMeta
|
||||||
var raceCode = Names.CombinedRace(manip.Gender, manip.Race).ToRaceCode();
|
var raceCode = Names.CombinedRace(manip.Gender, manip.Race).ToRaceCode();
|
||||||
return manip.Slot switch
|
return manip.Slot switch
|
||||||
{
|
{
|
||||||
EstManipulation.EstType.Hair => $"chara/human/c{raceCode}/obj/hair/h{manip.SetId:D4}/c{raceCode}h{manip.SetId:D4}_hir.meta",
|
EstType.Hair => $"chara/human/c{raceCode}/obj/hair/h{manip.SetId:D4}/c{raceCode}h{manip.SetId:D4}_hir.meta",
|
||||||
EstManipulation.EstType.Face => $"chara/human/c{raceCode}/obj/face/h{manip.SetId:D4}/c{raceCode}f{manip.SetId:D4}_fac.meta",
|
EstType.Face => $"chara/human/c{raceCode}/obj/face/h{manip.SetId:D4}/c{raceCode}f{manip.SetId:D4}_fac.meta",
|
||||||
EstManipulation.EstType.Body => $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{EquipSlot.Body.ToSuffix()}.meta",
|
EstType.Body => $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{EquipSlot.Body.ToSuffix()}.meta",
|
||||||
EstManipulation.EstType.Head => $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{EquipSlot.Head.ToSuffix()}.meta",
|
EstType.Head => $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{EquipSlot.Head.ToSuffix()}.meta",
|
||||||
_ => throw new ArgumentOutOfRangeException(),
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ public partial class TexToolsMeta
|
||||||
void Add(RspAttribute attribute, float value)
|
void Add(RspAttribute attribute, float value)
|
||||||
{
|
{
|
||||||
var def = CmpFile.GetDefault(manager, subRace, attribute);
|
var def = CmpFile.GetDefault(manager, subRace, attribute);
|
||||||
if (keepDefault || value != def)
|
if (keepDefault || value != def.Value)
|
||||||
ret.MetaManipulations.Add(new RspManipulation(subRace, attribute, value));
|
ret.MetaManipulations.Add(new RspManipulation(subRace, attribute, new RspEntry(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gender == 1)
|
if (gender == 1)
|
||||||
|
|
|
||||||
|
|
@ -212,10 +212,10 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
||||||
if (_parent.InInternalResolve)
|
if (_parent.InInternalResolve)
|
||||||
return DisposableContainer.Empty;
|
return DisposableContainer.Empty;
|
||||||
|
|
||||||
return new DisposableContainer(data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstManipulation.EstType.Face),
|
return new DisposableContainer(data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstType.Face),
|
||||||
data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstManipulation.EstType.Body),
|
data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstType.Body),
|
||||||
data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstManipulation.EstType.Hair),
|
data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstType.Hair),
|
||||||
data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstManipulation.EstType.Head));
|
data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstType.Head));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -250,30 +250,30 @@ internal partial record ResolveContext
|
||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return ResolveHumanExtraSkeletonData(characterRaceCode, EstManipulation.EstType.Face, faceId);
|
return ResolveHumanExtraSkeletonData(characterRaceCode, EstType.Face, faceId);
|
||||||
case 2:
|
case 2:
|
||||||
return ResolveHumanExtraSkeletonData(characterRaceCode, EstManipulation.EstType.Hair, human->HairId);
|
return ResolveHumanExtraSkeletonData(characterRaceCode, EstType.Hair, human->HairId);
|
||||||
case 3:
|
case 3:
|
||||||
return ResolveHumanEquipmentSkeletonData(EquipSlot.Head, EstManipulation.EstType.Head);
|
return ResolveHumanEquipmentSkeletonData(EquipSlot.Head, EstType.Head);
|
||||||
case 4:
|
case 4:
|
||||||
return ResolveHumanEquipmentSkeletonData(EquipSlot.Body, EstManipulation.EstType.Body);
|
return ResolveHumanEquipmentSkeletonData(EquipSlot.Body, EstType.Body);
|
||||||
default:
|
default:
|
||||||
return (0, string.Empty, 0);
|
return (0, string.Empty, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe (GenderRace RaceCode, string Slot, PrimaryId Set) ResolveHumanEquipmentSkeletonData(EquipSlot slot, EstManipulation.EstType type)
|
private unsafe (GenderRace RaceCode, string Slot, PrimaryId Set) ResolveHumanEquipmentSkeletonData(EquipSlot slot, EstType type)
|
||||||
{
|
{
|
||||||
var human = (Human*)CharacterBase;
|
var human = (Human*)CharacterBase;
|
||||||
var equipment = ((CharacterArmor*)&human->Head)[slot.ToIndex()];
|
var equipment = ((CharacterArmor*)&human->Head)[slot.ToIndex()];
|
||||||
return ResolveHumanExtraSkeletonData(ResolveEqdpRaceCode(slot, equipment.Set), type, equipment.Set);
|
return ResolveHumanExtraSkeletonData(ResolveEqdpRaceCode(slot, equipment.Set), type, equipment.Set);
|
||||||
}
|
}
|
||||||
|
|
||||||
private (GenderRace RaceCode, string Slot, PrimaryId Set) ResolveHumanExtraSkeletonData(GenderRace raceCode, EstManipulation.EstType type, PrimaryId primary)
|
private (GenderRace RaceCode, string Slot, PrimaryId Set) ResolveHumanExtraSkeletonData(GenderRace raceCode, EstType type, PrimaryId primary)
|
||||||
{
|
{
|
||||||
var metaCache = Global.Collection.MetaCache;
|
var metaCache = Global.Collection.MetaCache;
|
||||||
var skeletonSet = metaCache?.GetEstEntry(type, raceCode, primary) ?? default;
|
var skeletonSet = metaCache?.GetEstEntry(type, raceCode, primary) ?? default;
|
||||||
return (raceCode, EstManipulation.ToName(type), skeletonSet);
|
return (raceCode, EstManipulation.ToName(type), skeletonSet.AsId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe Utf8GamePath ResolveSkeletonPathNative(uint partialSkeletonIndex)
|
private unsafe Utf8GamePath ResolveSkeletonPathNative(uint partialSkeletonIndex)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Interop.Services;
|
using Penumbra.Interop.Services;
|
||||||
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.String.Functions;
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Files;
|
namespace Penumbra.Meta.Files;
|
||||||
|
|
@ -17,10 +18,10 @@ public sealed unsafe class CmpFile : MetaBaseFile
|
||||||
|
|
||||||
private const int RacialScalingStart = 0x2A800;
|
private const int RacialScalingStart = 0x2A800;
|
||||||
|
|
||||||
public float this[SubRace subRace, RspAttribute attribute]
|
public RspEntry this[SubRace subRace, RspAttribute attribute]
|
||||||
{
|
{
|
||||||
get => *(float*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4);
|
get => *(RspEntry*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4);
|
||||||
set => *(float*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4) = value;
|
set => *(RspEntry*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4) = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Reset()
|
public override void Reset()
|
||||||
|
|
@ -39,10 +40,10 @@ public sealed unsafe class CmpFile : MetaBaseFile
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float GetDefault(MetaFileManager manager, SubRace subRace, RspAttribute attribute)
|
public static RspEntry GetDefault(MetaFileManager manager, SubRace subRace, RspAttribute attribute)
|
||||||
{
|
{
|
||||||
var data = (byte*)manager.CharacterUtility.DefaultResource(InternalIndex).Address;
|
var data = (byte*)manager.CharacterUtility.DefaultResource(InternalIndex).Address;
|
||||||
return *(float*)(data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4);
|
return *(RspEntry*)(data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int ToRspIndex(SubRace subRace)
|
private static int ToRspIndex(SubRace subRace)
|
||||||
|
|
|
||||||
|
|
@ -34,26 +34,26 @@ public sealed unsafe class EstFile : MetaBaseFile
|
||||||
Removed,
|
Removed,
|
||||||
}
|
}
|
||||||
|
|
||||||
public ushort this[GenderRace genderRace, ushort setId]
|
public EstEntry this[GenderRace genderRace, PrimaryId setId]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var (idx, exists) = FindEntry(genderRace, setId);
|
var (idx, exists) = FindEntry(genderRace, setId);
|
||||||
if (!exists)
|
if (!exists)
|
||||||
return 0;
|
return EstEntry.Zero;
|
||||||
|
|
||||||
return *(ushort*)(Data + EntryDescSize * (Count + 1) + EntrySize * idx);
|
return *(EstEntry*)(Data + EntryDescSize * (Count + 1) + EntrySize * idx);
|
||||||
}
|
}
|
||||||
set => SetEntry(genderRace, setId, value);
|
set => SetEntry(genderRace, setId, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InsertEntry(int idx, GenderRace genderRace, ushort setId, ushort skeletonId)
|
private void InsertEntry(int idx, GenderRace genderRace, PrimaryId setId, EstEntry skeletonId)
|
||||||
{
|
{
|
||||||
if (Length < Size + EntryDescSize + EntrySize)
|
if (Length < Size + EntryDescSize + EntrySize)
|
||||||
ResizeResources(Length + IncreaseSize);
|
ResizeResources(Length + IncreaseSize);
|
||||||
|
|
||||||
var control = (Info*)(Data + 4);
|
var control = (Info*)(Data + 4);
|
||||||
var entries = (ushort*)(control + Count);
|
var entries = (EstEntry*)(control + Count);
|
||||||
|
|
||||||
for (var i = Count - 1; i >= idx; --i)
|
for (var i = Count - 1; i >= idx; --i)
|
||||||
entries[i + 3] = entries[i];
|
entries[i + 3] = entries[i];
|
||||||
|
|
@ -94,10 +94,10 @@ public sealed unsafe class EstFile : MetaBaseFile
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 4)]
|
[StructLayout(LayoutKind.Sequential, Size = 4)]
|
||||||
private struct Info : IComparable<Info>
|
private struct Info : IComparable<Info>
|
||||||
{
|
{
|
||||||
public readonly ushort SetId;
|
public readonly PrimaryId SetId;
|
||||||
public readonly GenderRace GenderRace;
|
public readonly GenderRace GenderRace;
|
||||||
|
|
||||||
public Info(GenderRace gr, ushort setId)
|
public Info(GenderRace gr, PrimaryId setId)
|
||||||
{
|
{
|
||||||
GenderRace = gr;
|
GenderRace = gr;
|
||||||
SetId = setId;
|
SetId = setId;
|
||||||
|
|
@ -106,42 +106,42 @@ public sealed unsafe class EstFile : MetaBaseFile
|
||||||
public int CompareTo(Info other)
|
public int CompareTo(Info other)
|
||||||
{
|
{
|
||||||
var genderRaceComparison = GenderRace.CompareTo(other.GenderRace);
|
var genderRaceComparison = GenderRace.CompareTo(other.GenderRace);
|
||||||
return genderRaceComparison != 0 ? genderRaceComparison : SetId.CompareTo(other.SetId);
|
return genderRaceComparison != 0 ? genderRaceComparison : SetId.Id.CompareTo(other.SetId.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (int, bool) FindEntry(ReadOnlySpan<Info> data, GenderRace genderRace, ushort setId)
|
private static (int, bool) FindEntry(ReadOnlySpan<Info> data, GenderRace genderRace, PrimaryId setId)
|
||||||
{
|
{
|
||||||
var idx = data.BinarySearch(new Info(genderRace, setId));
|
var idx = data.BinarySearch(new Info(genderRace, setId));
|
||||||
return idx < 0 ? (~idx, false) : (idx, true);
|
return idx < 0 ? (~idx, false) : (idx, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private (int, bool) FindEntry(GenderRace genderRace, ushort setId)
|
private (int, bool) FindEntry(GenderRace genderRace, PrimaryId setId)
|
||||||
{
|
{
|
||||||
var span = new ReadOnlySpan<Info>(Data + 4, Count);
|
var span = new ReadOnlySpan<Info>(Data + 4, Count);
|
||||||
return FindEntry(span, genderRace, setId);
|
return FindEntry(span, genderRace, setId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EstEntryChange SetEntry(GenderRace genderRace, ushort setId, ushort skeletonId)
|
public EstEntryChange SetEntry(GenderRace genderRace, PrimaryId setId, EstEntry skeletonId)
|
||||||
{
|
{
|
||||||
var (idx, exists) = FindEntry(genderRace, setId);
|
var (idx, exists) = FindEntry(genderRace, setId);
|
||||||
if (exists)
|
if (exists)
|
||||||
{
|
{
|
||||||
var value = *(ushort*)(Data + 4 * (Count + 1) + 2 * idx);
|
var value = *(EstEntry*)(Data + 4 * (Count + 1) + 2 * idx);
|
||||||
if (value == skeletonId)
|
if (value == skeletonId)
|
||||||
return EstEntryChange.Unchanged;
|
return EstEntryChange.Unchanged;
|
||||||
|
|
||||||
if (skeletonId == 0)
|
if (skeletonId == EstEntry.Zero)
|
||||||
{
|
{
|
||||||
RemoveEntry(idx);
|
RemoveEntry(idx);
|
||||||
return EstEntryChange.Removed;
|
return EstEntryChange.Removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
*(ushort*)(Data + 4 * (Count + 1) + 2 * idx) = skeletonId;
|
*(EstEntry*)(Data + 4 * (Count + 1) + 2 * idx) = skeletonId;
|
||||||
return EstEntryChange.Changed;
|
return EstEntryChange.Changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skeletonId == 0)
|
if (skeletonId == EstEntry.Zero)
|
||||||
return EstEntryChange.Unchanged;
|
return EstEntryChange.Unchanged;
|
||||||
|
|
||||||
InsertEntry(idx, genderRace, setId, skeletonId);
|
InsertEntry(idx, genderRace, setId, skeletonId);
|
||||||
|
|
@ -156,7 +156,7 @@ public sealed unsafe class EstFile : MetaBaseFile
|
||||||
MemoryUtility.MemSet(Data + length, 0, Length - length);
|
MemoryUtility.MemSet(Data + length, 0, Length - length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EstFile(MetaFileManager manager, EstManipulation.EstType estType)
|
public EstFile(MetaFileManager manager, EstType estType)
|
||||||
: base(manager, (MetaIndex)estType)
|
: base(manager, (MetaIndex)estType)
|
||||||
{
|
{
|
||||||
var length = DefaultData.Length;
|
var length = DefaultData.Length;
|
||||||
|
|
@ -164,24 +164,24 @@ public sealed unsafe class EstFile : MetaBaseFile
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ushort GetDefault(GenderRace genderRace, ushort setId)
|
public EstEntry GetDefault(GenderRace genderRace, PrimaryId setId)
|
||||||
=> GetDefault(Manager, Index, genderRace, setId);
|
=> GetDefault(Manager, Index, genderRace, setId);
|
||||||
|
|
||||||
public static ushort GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex index, GenderRace genderRace, PrimaryId primaryId)
|
public static EstEntry GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex index, GenderRace genderRace, PrimaryId primaryId)
|
||||||
{
|
{
|
||||||
var data = (byte*)manager.CharacterUtility.DefaultResource(index).Address;
|
var data = (byte*)manager.CharacterUtility.DefaultResource(index).Address;
|
||||||
var count = *(int*)data;
|
var count = *(int*)data;
|
||||||
var span = new ReadOnlySpan<Info>(data + 4, count);
|
var span = new ReadOnlySpan<Info>(data + 4, count);
|
||||||
var (idx, found) = FindEntry(span, genderRace, primaryId.Id);
|
var (idx, found) = FindEntry(span, genderRace, primaryId.Id);
|
||||||
if (!found)
|
if (!found)
|
||||||
return 0;
|
return EstEntry.Zero;
|
||||||
|
|
||||||
return *(ushort*)(data + 4 + count * EntryDescSize + idx * EntrySize);
|
return *(EstEntry*)(data + 4 + count * EntryDescSize + idx * EntrySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ushort GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, PrimaryId primaryId)
|
public static EstEntry GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, PrimaryId primaryId)
|
||||||
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, primaryId);
|
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, primaryId);
|
||||||
|
|
||||||
public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, PrimaryId primaryId)
|
public static EstEntry GetDefault(MetaFileManager manager, EstType estType, GenderRace genderRace, PrimaryId primaryId)
|
||||||
=> GetDefault(manager, (MetaIndex)estType, genderRace, primaryId);
|
=> GetDefault(manager, (MetaIndex)estType, genderRace, primaryId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
87
Penumbra/Meta/Manipulations/Eqdp.cs
Normal file
87
Penumbra/Meta/Manipulations/Eqdp.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
|
|
||||||
|
namespace Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
public readonly record struct EqdpIdentifier(PrimaryId SetId, EquipSlot Slot, GenderRace GenderRace)
|
||||||
|
: IMetaIdentifier, IComparable<EqdpIdentifier>
|
||||||
|
{
|
||||||
|
public ModelRace Race
|
||||||
|
=> GenderRace.Split().Item2;
|
||||||
|
|
||||||
|
public Gender Gender
|
||||||
|
=> GenderRace.Split().Item1;
|
||||||
|
|
||||||
|
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
|
||||||
|
=> identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace, Slot));
|
||||||
|
|
||||||
|
public MetaIndex FileIndex()
|
||||||
|
=> CharacterUtilityData.EqdpIdx(GenderRace, Slot.IsAccessory());
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> $"Eqdp - {SetId} - {Slot.ToName()} - {GenderRace.ToName()}";
|
||||||
|
|
||||||
|
public bool Validate()
|
||||||
|
{
|
||||||
|
var mask = Eqdp.Mask(Slot);
|
||||||
|
if (mask == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (FileIndex() == (MetaIndex)(-1))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// No check for set id.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(EqdpIdentifier other)
|
||||||
|
{
|
||||||
|
var gr = GenderRace.CompareTo(other.GenderRace);
|
||||||
|
if (gr != 0)
|
||||||
|
return gr;
|
||||||
|
|
||||||
|
var set = SetId.Id.CompareTo(other.SetId.Id);
|
||||||
|
if (set != 0)
|
||||||
|
return set;
|
||||||
|
|
||||||
|
return Slot.CompareTo(other.Slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EqdpIdentifier? FromJson(JObject jObj)
|
||||||
|
{
|
||||||
|
var gender = jObj["Gender"]?.ToObject<Gender>() ?? Gender.Unknown;
|
||||||
|
var race = jObj["Race"]?.ToObject<ModelRace>() ?? ModelRace.Unknown;
|
||||||
|
var setId = new PrimaryId(jObj["SetId"]?.ToObject<ushort>() ?? 0);
|
||||||
|
var slot = jObj["Slot"]?.ToObject<EquipSlot>() ?? EquipSlot.Unknown;
|
||||||
|
var ret = new EqdpIdentifier(setId, slot, Names.CombinedRace(gender, race));
|
||||||
|
return ret.Validate() ? ret : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JObject AddToJson(JObject jObj)
|
||||||
|
{
|
||||||
|
var (gender, race) = GenderRace.Split();
|
||||||
|
jObj["Gender"] = gender.ToString();
|
||||||
|
jObj["Race"] = race.ToString();
|
||||||
|
jObj["SetId"] = SetId.Id.ToString();
|
||||||
|
jObj["Slot"] = Slot.ToString();
|
||||||
|
return jObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct InternalEqdpEntry(bool Model, bool Material)
|
||||||
|
{
|
||||||
|
private InternalEqdpEntry((bool, bool) val)
|
||||||
|
: this(val.Item1, val.Item2)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public InternalEqdpEntry(EqdpEntry entry, EquipSlot slot)
|
||||||
|
: this(entry.ToBits(slot))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
public EqdpEntry ToEntry(EquipSlot slot)
|
||||||
|
=> Eqdp.FromSlotAndBits(slot, Model, Material);
|
||||||
|
}
|
||||||
|
|
@ -10,27 +10,30 @@ namespace Penumbra.Meta.Manipulations;
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public readonly struct EqdpManipulation : IMetaManipulation<EqdpManipulation>
|
public readonly struct EqdpManipulation : IMetaManipulation<EqdpManipulation>
|
||||||
{
|
{
|
||||||
public EqdpEntry Entry { get; private init; }
|
[JsonIgnore]
|
||||||
|
public EqdpIdentifier Identifier { get; private init; }
|
||||||
|
public EqdpEntry Entry { get; private init; }
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public Gender Gender { get; private init; }
|
public Gender Gender
|
||||||
|
=> Identifier.Gender;
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public ModelRace Race { get; private init; }
|
public ModelRace Race
|
||||||
|
=> Identifier.Race;
|
||||||
|
|
||||||
public PrimaryId SetId { get; private init; }
|
public PrimaryId SetId
|
||||||
|
=> Identifier.SetId;
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public EquipSlot Slot { get; private init; }
|
public EquipSlot Slot
|
||||||
|
=> Identifier.Slot;
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, PrimaryId setId)
|
public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, PrimaryId setId)
|
||||||
{
|
{
|
||||||
Gender = gender;
|
Identifier = new EqdpIdentifier(setId, slot, Names.CombinedRace(gender, race));
|
||||||
Race = race;
|
Entry = Eqdp.Mask(Slot) & entry;
|
||||||
SetId = setId;
|
|
||||||
Slot = slot;
|
|
||||||
Entry = Eqdp.Mask(Slot) & entry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public EqdpManipulation Copy(EqdpManipulation entry)
|
public EqdpManipulation Copy(EqdpManipulation entry)
|
||||||
|
|
|
||||||
72
Penumbra/Meta/Manipulations/Eqp.cs
Normal file
72
Penumbra/Meta/Manipulations/Eqp.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
|
|
||||||
|
namespace Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
public readonly record struct EqpIdentifier(PrimaryId SetId, EquipSlot Slot) : IMetaIdentifier, IComparable<EqpIdentifier>
|
||||||
|
{
|
||||||
|
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
|
||||||
|
=> identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace.MidlanderMale, Slot));
|
||||||
|
|
||||||
|
public MetaIndex FileIndex()
|
||||||
|
=> MetaIndex.Eqp;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> $"Eqp - {SetId} - {Slot}";
|
||||||
|
|
||||||
|
public bool Validate()
|
||||||
|
{
|
||||||
|
var mask = Eqp.Mask(Slot);
|
||||||
|
if (mask == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// No check for set id.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(EqpIdentifier other)
|
||||||
|
{
|
||||||
|
var set = SetId.Id.CompareTo(other.SetId.Id);
|
||||||
|
if (set != 0)
|
||||||
|
return set;
|
||||||
|
|
||||||
|
return Slot.CompareTo(other.Slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EqpIdentifier? FromJson(JObject jObj)
|
||||||
|
{
|
||||||
|
var setId = new PrimaryId(jObj["SetId"]?.ToObject<ushort>() ?? 0);
|
||||||
|
var slot = jObj["Slot"]?.ToObject<EquipSlot>() ?? EquipSlot.Unknown;
|
||||||
|
var ret = new EqpIdentifier(setId, slot);
|
||||||
|
return ret.Validate() ? ret : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JObject AddToJson(JObject jObj)
|
||||||
|
{
|
||||||
|
jObj["SetId"] = SetId.Id.ToString();
|
||||||
|
jObj["Slot"] = Slot.ToString();
|
||||||
|
return jObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct EqpEntryInternal(uint Value)
|
||||||
|
{
|
||||||
|
public EqpEntryInternal(EqpEntry entry, EquipSlot slot)
|
||||||
|
: this(GetValue(entry, slot))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public EqpEntry ToEntry(EquipSlot slot)
|
||||||
|
{
|
||||||
|
var (offset, mask) = Eqp.OffsetAndMask(slot);
|
||||||
|
return (EqpEntry)((ulong)Value << offset) & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint GetValue(EqpEntry entry, EquipSlot slot)
|
||||||
|
{
|
||||||
|
var (offset, mask) = Eqp.OffsetAndMask(slot);
|
||||||
|
return (uint)((ulong)(entry & mask) >> offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,6 @@ using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Meta.Files;
|
using Penumbra.Meta.Files;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
using SharpCompress.Common;
|
|
||||||
|
|
||||||
namespace Penumbra.Meta.Manipulations;
|
namespace Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
|
@ -15,17 +14,20 @@ public readonly struct EqpManipulation : IMetaManipulation<EqpManipulation>
|
||||||
[JsonConverter(typeof(ForceNumericFlagEnumConverter))]
|
[JsonConverter(typeof(ForceNumericFlagEnumConverter))]
|
||||||
public EqpEntry Entry { get; private init; }
|
public EqpEntry Entry { get; private init; }
|
||||||
|
|
||||||
public PrimaryId SetId { get; private init; }
|
public EqpIdentifier Identifier { get; private init; }
|
||||||
|
|
||||||
|
public PrimaryId SetId
|
||||||
|
=> Identifier.SetId;
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public EquipSlot Slot { get; private init; }
|
public EquipSlot Slot
|
||||||
|
=> Identifier.Slot;
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public EqpManipulation(EqpEntry entry, EquipSlot slot, PrimaryId setId)
|
public EqpManipulation(EqpEntry entry, EquipSlot slot, PrimaryId setId)
|
||||||
{
|
{
|
||||||
Slot = slot;
|
Identifier = new EqpIdentifier(setId, slot);
|
||||||
SetId = setId;
|
Entry = Eqp.Mask(slot) & entry;
|
||||||
Entry = Eqp.Mask(slot) & entry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public EqpManipulation Copy(EqpEntry entry)
|
public EqpManipulation Copy(EqpEntry entry)
|
||||||
|
|
|
||||||
113
Penumbra/Meta/Manipulations/Est.cs
Normal file
113
Penumbra/Meta/Manipulations/Est.cs
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
|
|
||||||
|
namespace Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
public enum EstType : byte
|
||||||
|
{
|
||||||
|
Hair = MetaIndex.HairEst,
|
||||||
|
Face = MetaIndex.FaceEst,
|
||||||
|
Body = MetaIndex.BodyEst,
|
||||||
|
Head = MetaIndex.HeadEst,
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct EstIdentifier(PrimaryId SetId, EstType Slot, GenderRace GenderRace)
|
||||||
|
: IMetaIdentifier, IComparable<EstIdentifier>
|
||||||
|
{
|
||||||
|
public ModelRace Race
|
||||||
|
=> GenderRace.Split().Item2;
|
||||||
|
|
||||||
|
public Gender Gender
|
||||||
|
=> GenderRace.Split().Item1;
|
||||||
|
|
||||||
|
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
|
||||||
|
{
|
||||||
|
switch (Slot)
|
||||||
|
{
|
||||||
|
case EstType.Hair:
|
||||||
|
changedItems.TryAdd(
|
||||||
|
$"Customization: {GenderRace.Split().Item2.ToName()} {GenderRace.Split().Item1.ToName()} Hair (Hair) {SetId}", null);
|
||||||
|
break;
|
||||||
|
case EstType.Face:
|
||||||
|
changedItems.TryAdd(
|
||||||
|
$"Customization: {GenderRace.Split().Item2.ToName()} {GenderRace.Split().Item1.ToName()} Face (Face) {SetId}", null);
|
||||||
|
break;
|
||||||
|
case EstType.Body:
|
||||||
|
identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace, EquipSlot.Body));
|
||||||
|
break;
|
||||||
|
case EstType.Head:
|
||||||
|
identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace, EquipSlot.Head));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MetaIndex FileIndex()
|
||||||
|
=> (MetaIndex)Slot;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> $"Est - {SetId} - {Slot} - {GenderRace.ToName()}";
|
||||||
|
|
||||||
|
public bool Validate()
|
||||||
|
{
|
||||||
|
if (!Enum.IsDefined(Slot))
|
||||||
|
return false;
|
||||||
|
if (GenderRace is GenderRace.Unknown || !Enum.IsDefined(GenderRace))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// No known check for set id.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(EstIdentifier other)
|
||||||
|
{
|
||||||
|
var gr = GenderRace.CompareTo(other.GenderRace);
|
||||||
|
if (gr != 0)
|
||||||
|
return gr;
|
||||||
|
|
||||||
|
var id = SetId.Id.CompareTo(other.SetId.Id);
|
||||||
|
return id != 0 ? id : Slot.CompareTo(other.Slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EstIdentifier? FromJson(JObject jObj)
|
||||||
|
{
|
||||||
|
var gender = jObj["Gender"]?.ToObject<Gender>() ?? Gender.Unknown;
|
||||||
|
var race = jObj["Race"]?.ToObject<ModelRace>() ?? ModelRace.Unknown;
|
||||||
|
var setId = new PrimaryId(jObj["SetId"]?.ToObject<ushort>() ?? 0);
|
||||||
|
var slot = jObj["Slot"]?.ToObject<EstType>() ?? 0;
|
||||||
|
var ret = new EstIdentifier(setId, slot, Names.CombinedRace(gender, race));
|
||||||
|
return ret.Validate() ? ret : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JObject AddToJson(JObject jObj)
|
||||||
|
{
|
||||||
|
var (gender, race) = GenderRace.Split();
|
||||||
|
jObj["Gender"] = gender.ToString();
|
||||||
|
jObj["Race"] = race.ToString();
|
||||||
|
jObj["SetId"] = SetId.Id.ToString();
|
||||||
|
jObj["Slot"] = Slot.ToString();
|
||||||
|
return jObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(Converter))]
|
||||||
|
public readonly record struct EstEntry(ushort Value)
|
||||||
|
{
|
||||||
|
public static readonly EstEntry Zero = new(0);
|
||||||
|
|
||||||
|
public PrimaryId AsId
|
||||||
|
=> new(Value);
|
||||||
|
|
||||||
|
private class Converter : JsonConverter<EstEntry>
|
||||||
|
{
|
||||||
|
public override void WriteJson(JsonWriter writer, EstEntry value, JsonSerializer serializer)
|
||||||
|
=> serializer.Serialize(writer, value.Value);
|
||||||
|
|
||||||
|
public override EstEntry ReadJson(JsonReader reader, Type objectType, EstEntry existingValue, bool hasExistingValue,
|
||||||
|
JsonSerializer serializer)
|
||||||
|
=> new(serializer.Deserialize<ushort>(reader));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,14 +10,6 @@ namespace Penumbra.Meta.Manipulations;
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public readonly struct EstManipulation : IMetaManipulation<EstManipulation>
|
public readonly struct EstManipulation : IMetaManipulation<EstManipulation>
|
||||||
{
|
{
|
||||||
public enum EstType : byte
|
|
||||||
{
|
|
||||||
Hair = MetaIndex.HairEst,
|
|
||||||
Face = MetaIndex.FaceEst,
|
|
||||||
Body = MetaIndex.BodyEst,
|
|
||||||
Head = MetaIndex.HeadEst,
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ToName(EstType type)
|
public static string ToName(EstType type)
|
||||||
=> type switch
|
=> type switch
|
||||||
{
|
{
|
||||||
|
|
@ -28,31 +20,33 @@ public readonly struct EstManipulation : IMetaManipulation<EstManipulation>
|
||||||
_ => "unk",
|
_ => "unk",
|
||||||
};
|
};
|
||||||
|
|
||||||
public ushort Entry { get; private init; } // SkeletonIdx.
|
public EstIdentifier Identifier { get; private init; }
|
||||||
|
public EstEntry Entry { get; private init; }
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public Gender Gender { get; private init; }
|
public Gender Gender
|
||||||
|
=> Identifier.Gender;
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public ModelRace Race { get; private init; }
|
public ModelRace Race
|
||||||
|
=> Identifier.Race;
|
||||||
|
|
||||||
public PrimaryId SetId { get; private init; }
|
public PrimaryId SetId
|
||||||
|
=> Identifier.SetId;
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public EstType Slot { get; private init; }
|
public EstType Slot
|
||||||
|
=> Identifier.Slot;
|
||||||
|
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public EstManipulation(Gender gender, ModelRace race, EstType slot, PrimaryId setId, ushort entry)
|
public EstManipulation(Gender gender, ModelRace race, EstType slot, PrimaryId setId, EstEntry entry)
|
||||||
{
|
{
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
Gender = gender;
|
Identifier = new EstIdentifier(setId, slot, Names.CombinedRace(gender, race));
|
||||||
Race = race;
|
|
||||||
SetId = setId;
|
|
||||||
Slot = slot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public EstManipulation Copy(ushort entry)
|
public EstManipulation Copy(EstEntry entry)
|
||||||
=> new(Gender, Race, Slot, SetId, entry);
|
=> new(Gender, Race, Slot, SetId, entry);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -111,3 +105,5 @@ public readonly struct EstManipulation : IMetaManipulation<EstManipulation>
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
39
Penumbra/Meta/Manipulations/Gmp.cs
Normal file
39
Penumbra/Meta/Manipulations/Gmp.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
|
|
||||||
|
namespace Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
public readonly record struct GmpIdentifier(PrimaryId SetId) : IMetaIdentifier, IComparable<GmpIdentifier>
|
||||||
|
{
|
||||||
|
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
|
||||||
|
=> identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace.MidlanderMale, EquipSlot.Head));
|
||||||
|
|
||||||
|
public MetaIndex FileIndex()
|
||||||
|
=> MetaIndex.Gmp;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> $"Gmp - {SetId}";
|
||||||
|
|
||||||
|
public bool Validate()
|
||||||
|
// No known conditions.
|
||||||
|
=> true;
|
||||||
|
|
||||||
|
public int CompareTo(GmpIdentifier other)
|
||||||
|
=> SetId.Id.CompareTo(other.SetId.Id);
|
||||||
|
|
||||||
|
public static GmpIdentifier? FromJson(JObject jObj)
|
||||||
|
{
|
||||||
|
var setId = new PrimaryId(jObj["SetId"]?.ToObject<ushort>() ?? 0);
|
||||||
|
var ret = new GmpIdentifier(setId);
|
||||||
|
return ret.Validate() ? ret : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JObject AddToJson(JObject jObj)
|
||||||
|
{
|
||||||
|
jObj["SetId"] = SetId.Id.ToString();
|
||||||
|
return jObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,14 +8,18 @@ namespace Penumbra.Meta.Manipulations;
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public readonly struct GmpManipulation : IMetaManipulation<GmpManipulation>
|
public readonly struct GmpManipulation : IMetaManipulation<GmpManipulation>
|
||||||
{
|
{
|
||||||
public GmpEntry Entry { get; private init; }
|
public GmpIdentifier Identifier { get; private init; }
|
||||||
public PrimaryId SetId { get; private init; }
|
|
||||||
|
public GmpEntry Entry { get; private init; }
|
||||||
|
|
||||||
|
public PrimaryId SetId
|
||||||
|
=> Identifier.SetId;
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public GmpManipulation(GmpEntry entry, PrimaryId setId)
|
public GmpManipulation(GmpEntry entry, PrimaryId setId)
|
||||||
{
|
{
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
SetId = setId;
|
Identifier = new GmpIdentifier(setId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GmpManipulation Copy(GmpEntry entry)
|
public GmpManipulation Copy(GmpEntry entry)
|
||||||
|
|
|
||||||
52
Penumbra/Meta/Manipulations/Rsp.cs
Normal file
52
Penumbra/Meta/Manipulations/Rsp.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
|
|
||||||
|
namespace Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
public readonly record struct RspIdentifier(SubRace SubRace, RspAttribute Attribute) : IMetaIdentifier
|
||||||
|
{
|
||||||
|
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
|
||||||
|
=> changedItems.TryAdd($"{SubRace.ToName()} {Attribute.ToFullString()}", null);
|
||||||
|
|
||||||
|
public MetaIndex FileIndex()
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
|
||||||
|
public bool Validate()
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
|
||||||
|
public JObject AddToJson(JObject jObj)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(Converter))]
|
||||||
|
public readonly record struct RspEntry(float Value) : IComparisonOperators<RspEntry, RspEntry, bool>
|
||||||
|
{
|
||||||
|
public const float MinValue = 0.01f;
|
||||||
|
public const float MaxValue = 512f;
|
||||||
|
public static readonly RspEntry One = new(1f);
|
||||||
|
|
||||||
|
private class Converter : JsonConverter<RspEntry>
|
||||||
|
{
|
||||||
|
public override void WriteJson(JsonWriter writer, RspEntry value, JsonSerializer serializer)
|
||||||
|
=> serializer.Serialize(writer, value.Value);
|
||||||
|
|
||||||
|
public override RspEntry ReadJson(JsonReader reader, Type objectType, RspEntry existingValue, bool hasExistingValue,
|
||||||
|
JsonSerializer serializer)
|
||||||
|
=> new(serializer.Deserialize<float>(reader));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator >(RspEntry left, RspEntry right)
|
||||||
|
=> left.Value > right.Value;
|
||||||
|
|
||||||
|
public static bool operator >=(RspEntry left, RspEntry right)
|
||||||
|
=> left.Value >= right.Value;
|
||||||
|
|
||||||
|
public static bool operator <(RspEntry left, RspEntry right)
|
||||||
|
=> left.Value < right.Value;
|
||||||
|
|
||||||
|
public static bool operator <=(RspEntry left, RspEntry right)
|
||||||
|
=> left.Value <= right.Value;
|
||||||
|
}
|
||||||
|
|
@ -9,25 +9,25 @@ namespace Penumbra.Meta.Manipulations;
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public readonly struct RspManipulation : IMetaManipulation<RspManipulation>
|
public readonly struct RspManipulation : IMetaManipulation<RspManipulation>
|
||||||
{
|
{
|
||||||
public const float MinValue = 0.01f;
|
public RspIdentifier Identifier { get; private init; }
|
||||||
public const float MaxValue = 512f;
|
public RspEntry Entry { get; private init; }
|
||||||
public float Entry { get; private init; }
|
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public SubRace SubRace { get; private init; }
|
public SubRace SubRace
|
||||||
|
=> Identifier.SubRace;
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public RspAttribute Attribute { get; private init; }
|
public RspAttribute Attribute
|
||||||
|
=> Identifier.Attribute;
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public RspManipulation(SubRace subRace, RspAttribute attribute, float entry)
|
public RspManipulation(SubRace subRace, RspAttribute attribute, RspEntry entry)
|
||||||
{
|
{
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
SubRace = subRace;
|
Identifier = new RspIdentifier(subRace, attribute);
|
||||||
Attribute = attribute;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RspManipulation Copy(float entry)
|
public RspManipulation Copy(RspEntry entry)
|
||||||
=> new(SubRace, Attribute, entry);
|
=> new(SubRace, Attribute, entry);
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
@ -68,7 +68,7 @@ public readonly struct RspManipulation : IMetaManipulation<RspManipulation>
|
||||||
return false;
|
return false;
|
||||||
if (!Enum.IsDefined(Attribute))
|
if (!Enum.IsDefined(Attribute))
|
||||||
return false;
|
return false;
|
||||||
if (Entry is < MinValue or > MaxValue)
|
if (Entry.Value is < RspEntry.MinValue or > RspEntry.MaxValue)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -126,9 +126,9 @@ public static class EquipmentSwap
|
||||||
var isAccessory = slot.IsAccessory();
|
var isAccessory = slot.IsAccessory();
|
||||||
var estType = slot switch
|
var estType = slot switch
|
||||||
{
|
{
|
||||||
EquipSlot.Head => EstManipulation.EstType.Head,
|
EquipSlot.Head => EstType.Head,
|
||||||
EquipSlot.Body => EstManipulation.EstType.Body,
|
EquipSlot.Body => EstType.Body,
|
||||||
_ => (EstManipulation.EstType)0,
|
_ => (EstType)0,
|
||||||
};
|
};
|
||||||
|
|
||||||
var skipFemale = false;
|
var skipFemale = false;
|
||||||
|
|
|
||||||
|
|
@ -133,23 +133,23 @@ public static class ItemSwap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static FileSwap CreatePhyb(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EstManipulation.EstType type,
|
public static FileSwap CreatePhyb(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EstType type,
|
||||||
GenderRace race, ushort estEntry)
|
GenderRace race, EstEntry estEntry)
|
||||||
{
|
{
|
||||||
var phybPath = GamePaths.Skeleton.Phyb.Path(race, EstManipulation.ToName(type), estEntry);
|
var phybPath = GamePaths.Skeleton.Phyb.Path(race, EstManipulation.ToName(type), estEntry.AsId);
|
||||||
return FileSwap.CreateSwap(manager, ResourceType.Phyb, redirections, phybPath, phybPath);
|
return FileSwap.CreateSwap(manager, ResourceType.Phyb, redirections, phybPath, phybPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileSwap CreateSklb(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EstManipulation.EstType type,
|
public static FileSwap CreateSklb(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EstType type,
|
||||||
GenderRace race, ushort estEntry)
|
GenderRace race, EstEntry estEntry)
|
||||||
{
|
{
|
||||||
var sklbPath = GamePaths.Skeleton.Sklb.Path(race, EstManipulation.ToName(type), estEntry);
|
var sklbPath = GamePaths.Skeleton.Sklb.Path(race, EstManipulation.ToName(type), estEntry.AsId);
|
||||||
return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath);
|
return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks>
|
/// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks>
|
||||||
public static MetaSwap? CreateEst(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
public static MetaSwap? CreateEst(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||||
Func<MetaManipulation, MetaManipulation> manips, EstManipulation.EstType type,
|
Func<MetaManipulation, MetaManipulation> manips, EstType type,
|
||||||
GenderRace genderRace, PrimaryId idFrom, PrimaryId idTo, bool ownMdl)
|
GenderRace genderRace, PrimaryId idFrom, PrimaryId idTo, bool ownMdl)
|
||||||
{
|
{
|
||||||
if (type == 0)
|
if (type == 0)
|
||||||
|
|
@ -160,7 +160,7 @@ public static class ItemSwap
|
||||||
var toDefault = new EstManipulation(gender, race, type, idTo, EstFile.GetDefault(manager, type, genderRace, idTo));
|
var toDefault = new EstManipulation(gender, race, type, idTo, EstFile.GetDefault(manager, type, genderRace, idTo));
|
||||||
var est = new MetaSwap(manips, fromDefault, toDefault);
|
var est = new MetaSwap(manips, fromDefault, toDefault);
|
||||||
|
|
||||||
if (ownMdl && est.SwapApplied.Est.Entry >= 2)
|
if (ownMdl && est.SwapApplied.Est.Entry.Value >= 2)
|
||||||
{
|
{
|
||||||
var phyb = CreatePhyb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry);
|
var phyb = CreatePhyb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry);
|
||||||
est.ChildSwaps.Add(phyb);
|
est.ChildSwaps.Add(phyb);
|
||||||
|
|
|
||||||
|
|
@ -152,9 +152,9 @@ public class ItemSwapContainer
|
||||||
var mdl = CustomizationSwap.CreateMdl(manager, pathResolver, slot, race, from, to);
|
var mdl = CustomizationSwap.CreateMdl(manager, pathResolver, slot, race, from, to);
|
||||||
var type = slot switch
|
var type = slot switch
|
||||||
{
|
{
|
||||||
BodySlot.Hair => EstManipulation.EstType.Hair,
|
BodySlot.Hair => EstType.Hair,
|
||||||
BodySlot.Face => EstManipulation.EstType.Face,
|
BodySlot.Face => EstType.Face,
|
||||||
_ => (EstManipulation.EstType)0,
|
_ => (EstType)0,
|
||||||
};
|
};
|
||||||
|
|
||||||
var metaResolver = MetaResolver(collection);
|
var metaResolver = MetaResolver(collection);
|
||||||
|
|
|
||||||
|
|
@ -453,7 +453,7 @@ public partial class ModEditWindow
|
||||||
|
|
||||||
private static class EstRow
|
private static class EstRow
|
||||||
{
|
{
|
||||||
private static EstManipulation _new = new(Gender.Male, ModelRace.Midlander, EstManipulation.EstType.Body, 1, 0);
|
private static EstManipulation _new = new(Gender.Male, ModelRace.Midlander, EstType.Body, 1, EstEntry.Zero);
|
||||||
|
|
||||||
private static float IdWidth
|
private static float IdWidth
|
||||||
=> 100 * UiHelpers.Scale;
|
=> 100 * UiHelpers.Scale;
|
||||||
|
|
@ -510,7 +510,7 @@ public partial class ModEditWindow
|
||||||
// Values
|
// Values
|
||||||
using var disabled = ImRaii.Disabled();
|
using var disabled = ImRaii.Disabled();
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
IntDragInput("##estSkeleton", "Skeleton Index", IdWidth, _new.Entry, defaultEntry, out _, 0, ushort.MaxValue, 0.05f);
|
IntDragInput("##estSkeleton", "Skeleton Index", IdWidth, _new.Entry.Value, defaultEntry.Value, out _, 0, ushort.MaxValue, 0.05f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Draw(MetaFileManager metaFileManager, EstManipulation meta, ModEditor editor, Vector2 iconSize)
|
public static void Draw(MetaFileManager metaFileManager, EstManipulation meta, ModEditor editor, Vector2 iconSize)
|
||||||
|
|
@ -538,9 +538,9 @@ public partial class ModEditWindow
|
||||||
// Values
|
// Values
|
||||||
var defaultEntry = EstFile.GetDefault(metaFileManager, meta.Slot, Names.CombinedRace(meta.Gender, meta.Race), meta.SetId);
|
var defaultEntry = EstFile.GetDefault(metaFileManager, meta.Slot, Names.CombinedRace(meta.Gender, meta.Race), meta.SetId);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (IntDragInput("##estSkeleton", $"Skeleton Index\nDefault Value: {defaultEntry}", IdWidth, meta.Entry, defaultEntry,
|
if (IntDragInput("##estSkeleton", $"Skeleton Index\nDefault Value: {defaultEntry}", IdWidth, meta.Entry.Value, defaultEntry.Value,
|
||||||
out var entry, 0, ushort.MaxValue, 0.05f))
|
out var entry, 0, ushort.MaxValue, 0.05f))
|
||||||
editor.MetaEditor.Change(meta.Copy((ushort)entry));
|
editor.MetaEditor.Change(meta.Copy(new EstEntry((ushort)entry)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -646,7 +646,7 @@ public partial class ModEditWindow
|
||||||
|
|
||||||
private static class RspRow
|
private static class RspRow
|
||||||
{
|
{
|
||||||
private static RspManipulation _new = new(SubRace.Midlander, RspAttribute.MaleMinSize, 1f);
|
private static RspManipulation _new = new(SubRace.Midlander, RspAttribute.MaleMinSize, RspEntry.One);
|
||||||
|
|
||||||
private static float FloatWidth
|
private static float FloatWidth
|
||||||
=> 150 * UiHelpers.Scale;
|
=> 150 * UiHelpers.Scale;
|
||||||
|
|
@ -680,7 +680,8 @@ public partial class ModEditWindow
|
||||||
using var disabled = ImRaii.Disabled();
|
using var disabled = ImRaii.Disabled();
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.SetNextItemWidth(FloatWidth);
|
ImGui.SetNextItemWidth(FloatWidth);
|
||||||
ImGui.DragFloat("##rspValue", ref defaultEntry, 0f);
|
var value = defaultEntry.Value;
|
||||||
|
ImGui.DragFloat("##rspValue", ref value, 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Draw(MetaFileManager metaFileManager, RspManipulation meta, ModEditor editor, Vector2 iconSize)
|
public static void Draw(MetaFileManager metaFileManager, RspManipulation meta, ModEditor editor, Vector2 iconSize)
|
||||||
|
|
@ -699,15 +700,15 @@ public partial class ModEditWindow
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
// Values
|
// Values
|
||||||
var def = CmpFile.GetDefault(metaFileManager, meta.SubRace, meta.Attribute);
|
var def = CmpFile.GetDefault(metaFileManager, meta.SubRace, meta.Attribute).Value;
|
||||||
var value = meta.Entry;
|
var value = meta.Entry.Value;
|
||||||
ImGui.SetNextItemWidth(FloatWidth);
|
ImGui.SetNextItemWidth(FloatWidth);
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
|
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
|
||||||
def < value ? ColorId.IncreasedMetaValue.Value() : ColorId.DecreasedMetaValue.Value(),
|
def < value ? ColorId.IncreasedMetaValue.Value() : ColorId.DecreasedMetaValue.Value(),
|
||||||
def != value);
|
def != value);
|
||||||
if (ImGui.DragFloat("##rspValue", ref value, 0.001f, RspManipulation.MinValue, RspManipulation.MaxValue)
|
if (ImGui.DragFloat("##rspValue", ref value, 0.001f, RspEntry.MinValue, RspEntry.MaxValue)
|
||||||
&& value is >= RspManipulation.MinValue and <= RspManipulation.MaxValue)
|
&& value is >= RspEntry.MinValue and <= RspEntry.MaxValue)
|
||||||
editor.MetaEditor.Change(meta.Copy(value));
|
editor.MetaEditor.Change(meta.Copy(new RspEntry(value)));
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip($"Default Value: {def:0.###}");
|
ImGuiUtil.HoverTooltip($"Default Value: {def:0.###}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ public static class Combos
|
||||||
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out attribute,
|
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out attribute,
|
||||||
RspAttributeExtensions.ToFullString, 0, 1);
|
RspAttributeExtensions.ToFullString, 0, 1);
|
||||||
|
|
||||||
public static bool EstSlot(string label, EstManipulation.EstType current, out EstManipulation.EstType attribute, float unscaledWidth = 200)
|
public static bool EstSlot(string label, EstType current, out EstType attribute, float unscaledWidth = 200)
|
||||||
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out attribute);
|
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out attribute);
|
||||||
|
|
||||||
public static bool ImcType(string label, ObjectType current, out ObjectType type, float unscaledWidth = 110)
|
public static bool ImcType(string label, ObjectType current, out ObjectType type, float unscaledWidth = 110)
|
||||||
|
|
|
||||||
|
|
@ -51,18 +51,18 @@ public static class IdentifierExtensions
|
||||||
case MetaManipulation.Type.Est:
|
case MetaManipulation.Type.Est:
|
||||||
switch (manip.Est.Slot)
|
switch (manip.Est.Slot)
|
||||||
{
|
{
|
||||||
case EstManipulation.EstType.Hair:
|
case EstType.Hair:
|
||||||
changedItems.TryAdd($"Customization: {manip.Est.Race} {manip.Est.Gender} Hair (Hair) {manip.Est.SetId}", null);
|
changedItems.TryAdd($"Customization: {manip.Est.Race} {manip.Est.Gender} Hair (Hair) {manip.Est.SetId}", null);
|
||||||
break;
|
break;
|
||||||
case EstManipulation.EstType.Face:
|
case EstType.Face:
|
||||||
changedItems.TryAdd($"Customization: {manip.Est.Race} {manip.Est.Gender} Face (Face) {manip.Est.SetId}", null);
|
changedItems.TryAdd($"Customization: {manip.Est.Race} {manip.Est.Gender} Face (Face) {manip.Est.SetId}", null);
|
||||||
break;
|
break;
|
||||||
case EstManipulation.EstType.Body:
|
case EstType.Body:
|
||||||
identifier.Identify(changedItems,
|
identifier.Identify(changedItems,
|
||||||
GamePaths.Equipment.Mdl.Path(manip.Est.SetId, Names.CombinedRace(manip.Est.Gender, manip.Est.Race),
|
GamePaths.Equipment.Mdl.Path(manip.Est.SetId, Names.CombinedRace(manip.Est.Gender, manip.Est.Race),
|
||||||
EquipSlot.Body));
|
EquipSlot.Body));
|
||||||
break;
|
break;
|
||||||
case EstManipulation.EstType.Head:
|
case EstType.Head:
|
||||||
identifier.Identify(changedItems,
|
identifier.Identify(changedItems,
|
||||||
GamePaths.Equipment.Mdl.Path(manip.Est.SetId, Names.CombinedRace(manip.Est.Gender, manip.Est.Race),
|
GamePaths.Equipment.Mdl.Path(manip.Est.SetId, Names.CombinedRace(manip.Est.Gender, manip.Est.Race),
|
||||||
EquipSlot.Head));
|
EquipSlot.Head));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue