diff --git a/Penumbra/Game/EqpEntry.cs b/Penumbra/Game/EqpEntry.cs index 2bc9ac75..98ca2e6d 100644 --- a/Penumbra/Game/EqpEntry.cs +++ b/Penumbra/Game/EqpEntry.cs @@ -32,7 +32,7 @@ namespace Penumbra.Game LegsHideBootsM = 0x08ul << 16, _20 = 0x10ul << 16, LegsShowFoot = 0x20ul << 16, - _22 = 0x40ul << 16, + LegsShowTail = 0x40ul << 16, _23 = 0x80ul << 16, LegsMask = 0xFFul << 16, diff --git a/Penumbra/Meta/Files/ImcExtensions.cs b/Penumbra/Meta/Files/ImcExtensions.cs index 9be5a673..c4c62552 100644 --- a/Penumbra/Meta/Files/ImcExtensions.cs +++ b/Penumbra/Meta/Files/ImcExtensions.cs @@ -21,13 +21,35 @@ namespace Penumbra.Meta.Files public static ulong ToInteger( this ImcFile.ImageChangeData imc ) { ulong ret = imc.MaterialId; - ret |= ( ulong )imc.DecalId << 8; - ret |= ( ulong )imc.AttributeMask << 16; - ret |= ( ulong )imc.SoundId << 16; - ret |= ( ulong )imc.VfxId << 32; + ret |= ( ulong )imc.DecalId << 8; + ret |= ( ulong )imc.AttributeMask << 16; + ret |= ( ulong )imc.SoundId << 16; + ret |= ( ulong )imc.VfxId << 32; + ret |= ( ulong )imc.ActualMaterialAnimationId() << 40; + return ret; + } + + public static byte ActualMaterialAnimationId( this ImcFile.ImageChangeData imc ) + { var tmp = imc.GetType().GetField( "_MaterialAnimationIdMask", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance ); - ret |= ( ulong )( byte )tmp!.GetValue( imc ) << 40; + return ( byte )( tmp?.GetValue( imc ) ?? 0 ); + } + + public static ImcFile.ImageChangeData FromValues( byte materialId, byte decalId, ushort attributeMask, byte soundId, byte vfxId, + byte materialAnimationId ) + { + var ret = new ImcFile.ImageChangeData() + { + DecalId = decalId, + MaterialId = materialId, + VfxId = vfxId, + }; + ret.GetType().GetField( "_AttributeAndSound", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance )! + .SetValue( ret, ( ushort )( ( attributeMask & 0x3FF ) | ( soundId << 10 ) ) ); + ret.GetType().GetField( "_AttributeAndSound", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance )!.SetValue( ret, materialAnimationId ); return ret; } @@ -45,7 +67,7 @@ namespace Penumbra.Meta.Files bw.Write( variant.DecalId ); bw.Write( ( ushort )( variant.AttributeMask | variant.SoundId ) ); bw.Write( variant.VfxId ); - bw.Write( variant.MaterialAnimationId ); + bw.Write( variant.ActualMaterialAnimationId() ); } public static byte[] WriteBytes( this ImcFile file ) diff --git a/Penumbra/Meta/Files/MetaDefaults.cs b/Penumbra/Meta/Files/MetaDefaults.cs index c84f3894..f73fc1b6 100644 --- a/Penumbra/Meta/Files/MetaDefaults.cs +++ b/Penumbra/Meta/Files/MetaDefaults.cs @@ -145,6 +145,24 @@ namespace Penumbra.Meta.Files }; } + public object? GetDefaultValue( MetaManipulation m ) + { + return m.Type switch + { + MetaType.Imc => GetDefaultImcFile( m.ImcIdentifier.ObjectType, m.ImcIdentifier.PrimaryId, m.ImcIdentifier.SecondaryId ) + ?.GetValue( m ), + MetaType.Gmp => GetDefaultGmpFile()?.GetEntry( m.GmpIdentifier.SetId ), + MetaType.Eqp => GetDefaultEqpFile()?.GetEntry( m.EqpIdentifier.SetId ) + .Reduce( m.EqpIdentifier.Slot ), + MetaType.Eqdp => GetDefaultEqdpFile( m.EqdpIdentifier.Slot, m.EqdpIdentifier.GenderRace )?.GetEntry( m.EqdpIdentifier.SetId ) + .Reduce( m.EqdpIdentifier.Slot ), + MetaType.Est => GetDefaultEstFile( m.EstIdentifier.ObjectType, m.EstIdentifier.EquipSlot, m.EstIdentifier.BodySlot ) + ?.GetEntry( m.EstIdentifier.GenderRace, m.EstIdentifier.PrimaryId ), + MetaType.Rsp => GetDefaultCmpFile()?[ m.RspIdentifier.SubRace ][ m.RspIdentifier.Attribute ], + _ => throw new NotImplementedException(), + }; + } + // Create a deep copy of a default file as a new file. public object? CreateNewFile( MetaManipulation m ) { diff --git a/Penumbra/Mods/CollectionManager.cs b/Penumbra/Mods/CollectionManager.cs index f350c5f6..99cf8af7 100644 --- a/Penumbra/Mods/CollectionManager.cs +++ b/Penumbra/Mods/CollectionManager.cs @@ -50,7 +50,7 @@ namespace Penumbra.Mods } } - internal void UpdateCollections( ModData mod, bool metaChanges, ResourceChange fileChanges, bool nameChange, bool recomputeMeta ) + internal void UpdateCollections( ModData mod, bool metaChanges, ResourceChange fileChanges, bool nameChange, bool reloadMeta ) { foreach( var collection in Collections.Values ) { @@ -70,13 +70,13 @@ namespace Penumbra.Mods collection.Cache?.CalculateEffectiveFileList(); } - if( recomputeMeta ) + if( reloadMeta ) { collection.Cache?.UpdateMetaManipulations(); } } - if( recomputeMeta ) + if( reloadMeta && ActiveCollection.Settings.TryGetValue( mod.BasePath.Name, out var config ) && config.Enabled ) { Service< GameResourceManagement >.Get().ReloadPlayerResources(); } diff --git a/Penumbra/Mods/ModManager.cs b/Penumbra/Mods/ModManager.cs index c04e0c11..f78d62d2 100644 --- a/Penumbra/Mods/ModManager.cs +++ b/Penumbra/Mods/ModManager.cs @@ -152,7 +152,7 @@ namespace Penumbra.Mods return true; } - public bool UpdateMod( ModData mod, bool recomputeMeta = false ) + public bool UpdateMod( ModData mod, bool reloadMeta = false, bool recomputeMeta = false ) { var oldName = mod.Meta.Name; var metaChanges = mod.Meta.RefreshFromFile( mod.MetaFile ); @@ -177,7 +177,7 @@ namespace Penumbra.Mods mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) ); } - Collections.UpdateCollections( mod, metaChanges, fileChanges, nameChange, recomputeMeta ); + Collections.UpdateCollections( mod, metaChanges, fileChanges, nameChange, reloadMeta ); return true; } diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs index 4d784be7..d2725c9e 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs @@ -54,10 +54,13 @@ namespace Penumbra.UI private const float OptionSelectionWidth = 140f; private const float CheckMarkSize = 50f; + private const uint ColorDarkGreen = 0xFF00A000; private const uint ColorGreen = 0xFF00C800; private const uint ColorYellow = 0xFF00C8C8; + private const uint ColorDarkRed = 0xFF0000A0; private const uint ColorRed = 0xFF0000C8; + private bool _editMode; private int _selectedGroupIndex; private OptionGroup? _selectedGroup; @@ -649,149 +652,6 @@ namespace Penumbra.UI } } - private static void DrawManipulationRow( MetaManipulation manip ) - { - ImGui.TableNextColumn(); - ImGui.Text( manip.Type.ToString() ); - ImGui.TableNextColumn(); - - switch( manip.Type ) - { - case MetaType.Eqp: - { - ImGui.Text( manip.EqpIdentifier.Slot.IsAccessory() - ? ObjectType.Accessory.ToString() - : ObjectType.Equipment.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( manip.EqpIdentifier.SetId.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( manip.EqpIdentifier.Slot.ToString() ); - break; - } - case MetaType.Gmp: - { - ImGui.Text( ObjectType.Equipment.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( manip.GmpIdentifier.SetId.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( EquipSlot.Head.ToString() ); - break; - } - case MetaType.Eqdp: - { - ImGui.Text( manip.EqpIdentifier.Slot.IsAccessory() - ? ObjectType.Accessory.ToString() - : ObjectType.Equipment.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( manip.EqdpIdentifier.SetId.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( manip.EqpIdentifier.Slot.ToString() ); - ImGui.TableNextColumn(); - var (gender, race) = manip.EqdpIdentifier.GenderRace.Split(); - ImGui.Text( race.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( gender.ToString() ); - break; - } - case MetaType.Est: - { - ImGui.Text( manip.EstIdentifier.ObjectType.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( manip.EstIdentifier.PrimaryId.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( manip.EstIdentifier.ObjectType == ObjectType.Equipment - ? manip.EstIdentifier.EquipSlot.ToString() - : manip.EstIdentifier.BodySlot.ToString() ); - ImGui.TableNextColumn(); - var (gender, race) = manip.EstIdentifier.GenderRace.Split(); - ImGui.Text( race.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( gender.ToString() ); - break; - } - case MetaType.Imc: - { - ImGui.Text( manip.ImcIdentifier.ObjectType.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( manip.ImcIdentifier.PrimaryId.ToString() ); - ImGui.TableNextColumn(); - if( manip.ImcIdentifier.ObjectType == ObjectType.Accessory - || manip.ImcIdentifier.ObjectType == ObjectType.Equipment ) - { - ImGui.Text( manip.ImcIdentifier.ObjectType == ObjectType.Equipment - || manip.ImcIdentifier.ObjectType == ObjectType.Accessory - ? manip.ImcIdentifier.EquipSlot.ToString() - : manip.ImcIdentifier.BodySlot.ToString() ); - } - - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - if( manip.ImcIdentifier.ObjectType != ObjectType.Equipment - && manip.ImcIdentifier.ObjectType != ObjectType.Accessory ) - { - ImGui.Text( manip.ImcIdentifier.SecondaryId.ToString() ); - } - - ImGui.TableNextColumn(); - ImGui.Text( manip.ImcIdentifier.Variant.ToString() ); - break; - } - case MetaType.Rsp: - { - ImGui.Text( manip.RspIdentifier.Attribute.ToUngenderedString() ); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.Text( manip.RspIdentifier.SubRace.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( manip.RspIdentifier.Attribute.ToGender().ToString() ); - break; - } - } - - ImGui.TableSetColumnIndex( 9 ); - ImGui.Text( manip.Type == MetaType.Rsp ? manip.RspValue.ToString( CultureInfo.InvariantCulture ) : manip.Value.ToString() ); - ImGui.TableNextRow(); - } - - private static void DrawMetaManipulationsTable( string label, List< MetaManipulation > list ) - { - if( list.Count == 0 - || !ImGui.BeginTable( label, 10, - ImGuiTableFlags.BordersInner | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit ) ) - { - return; - } - - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Type##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Object Type##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Set##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Slot##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Race##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Gender##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Secondary ID##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Variant##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Value##{label}" ); - ImGui.TableNextRow(); - foreach( var manip in list ) - { - DrawManipulationRow( manip ); - } - - ImGui.EndTable(); - } - private void DrawMetaManipulationsTab() { if( Mod.Data.Resources.MetaManipulations.Count == 0 ) @@ -806,26 +666,33 @@ namespace Penumbra.UI if( ImGui.BeginListBox( "##MetaManipulations", AutoFillSize ) ) { - var manips = Mod.Data.Resources.MetaManipulations; - if( manips.DefaultData.Count > 0 ) + var manips = Mod.Data.Resources.MetaManipulations; + var changes = false; + if( _editMode || manips.DefaultData.Count > 0 ) { if( ImGui.CollapsingHeader( "Default" ) ) { - DrawMetaManipulationsTable( "##DefaultManips", manips.DefaultData ); + changes = DrawMetaManipulationsTable( "##DefaultManips", manips.DefaultData ); } } foreach( var group in manips.GroupData ) { - foreach( var option in @group.Value ) + foreach( var option in group.Value ) { - if( ImGui.CollapsingHeader( $"{@group.Key} - {option.Key}" ) ) + if( ImGui.CollapsingHeader( $"{group.Key} - {option.Key}" ) ) { - DrawMetaManipulationsTable( $"##{@group.Key}{option.Key}manips", option.Value ); + changes |= DrawMetaManipulationsTable( $"##{group.Key}{option.Key}manips", option.Value ); } } } + if( changes ) + { + Mod.Data.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( Mod.Data.BasePath ) ); + _selector.ReloadCurrentMod( true, false ); + } + ImGui.EndListBox(); } diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs new file mode 100644 index 00000000..4428209b --- /dev/null +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs @@ -0,0 +1,877 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using ImGuiNET; +using Lumina.Data.Files; +using Penumbra.Game; +using Penumbra.Game.Enums; +using Penumbra.Meta; +using Penumbra.Meta.Files; +using Penumbra.Util; +using ObjectType = Penumbra.Game.Enums.ObjectType; + +namespace Penumbra.UI +{ + public partial class SettingsInterface + { + private partial class PluginDetails + { + private int _newManipTypeIdx = 0; + private ushort _newManipSetId = 0; + private ushort _newManipSecondaryId = 0; + private int _newManipSubrace = 0; + private int _newManipRace = 0; + private int _newManipAttribute = 0; + private int _newManipEquipSlot = 0; + private int _newManipObjectType = 0; + private int _newManipGender = 0; + private int _newManipBodySlot = 0; + private ushort _newManipVariant = 0; + + private static readonly (string, EqpEntry)[] EqpAttributesBody = + { + ( "Enabled", EqpEntry.BodyEnabled ), + ( "Hide Waist", EqpEntry.BodyHideWaist ), + ( "Hide Small Gloves", EqpEntry.BodyHideGlovesS ), + ( "Hide Medium Gloves", EqpEntry.BodyHideGlovesM ), + ( "Hide Large Gloves", EqpEntry.BodyHideGlovesL ), + ( "Hide Gorget", EqpEntry.BodyHideGorget ), + ( "Show Legs", EqpEntry.BodyShowLeg ), + ( "Show Hands", EqpEntry.BodyShowHand ), + ( "Show Head", EqpEntry.BodyShowHead ), + ( "Show Necklace", EqpEntry.BodyShowNecklace ), + ( "Show Bracelet", EqpEntry.BodyShowBracelet ), + ( "Show Tail", EqpEntry.BodyShowTail ), + ( "Unknown 2", EqpEntry._2 ), + ( "Unknown 4", EqpEntry._4 ), + ( "Unknown 14", EqpEntry._14 ), + ( "Unknown 15", EqpEntry._15 ), + }; + + private static readonly (string, EqpEntry)[] EqpAttributesLegs = + { + ( "Enabled", EqpEntry.LegsEnabled ), + ( "Hide Kneepads", EqpEntry.LegsHideKneePads ), + ( "Hide Small Boots", EqpEntry.LegsHideBootsS ), + ( "Hide Medium Boots", EqpEntry.LegsHideBootsM ), + ( "Hide Show Foot", EqpEntry.LegsShowFoot ), + ( "Hide Show Tail", EqpEntry.LegsShowTail ), + ( "Unknown 20", EqpEntry._20 ), + ( "Unknown 23", EqpEntry._23 ), + }; + + private static readonly (string, EqpEntry)[] EqpAttributesHands = + { + ( "Enabled", EqpEntry.HandsEnabled ), + ( "Hide Elbow", EqpEntry.HandsHideElbow ), + ( "Hide Forearm", EqpEntry.HandsHideForearm ), + ( "Show Bracelet", EqpEntry.HandShowBracelet ), + ( "Show Left Ring", EqpEntry.HandShowRingL ), + ( "Show Right Ring", EqpEntry.HandShowRingR ), + ( "Unknown 27", EqpEntry._27 ), + ( "Unknown 31", EqpEntry._31 ), + }; + + private static readonly (string, EqpEntry)[] EqpAttributesFeet = + { + ( "Enabled", EqpEntry.FeetEnabled ), + ( "Hide Knees", EqpEntry.FeetHideKnee ), + ( "Hide Calfs", EqpEntry.FeetHideCalf ), + ( "Hide Ankles", EqpEntry.FeetHideAnkle ), + ( "Unknown 36", EqpEntry._36 ), + ( "Unknown 37", EqpEntry._37 ), + ( "Unknown 38", EqpEntry._38 ), + ( "Unknown 39", EqpEntry._39 ), + }; + + private static readonly (string, EqpEntry)[] EqpAttributesHead = + { + ( "Enabled", EqpEntry.HeadEnabled ), + ( "Hide Scalp", EqpEntry.HeadHideScalp ), + ( "Hide Hair", EqpEntry.HeadHideHair ), + ( "Show Hair Override", EqpEntry.HeadShowHairOverride ), + ( "Hide Neck", EqpEntry.HeadHideNeck ), + ( "Show Necklace", EqpEntry.HeadShowNecklace ), + ( "Show Earrings", EqpEntry.HeadShowEarrings ), + ( "Show Earrings (Human)", EqpEntry.HeadShowEarringsHuman ), + ( "Show Earrings (Au Ra)", EqpEntry.HeadShowEarringsAura ), + ( "Show Ears (Human)", EqpEntry.HeadShowEarHuman ), + ( "Show Ears (Miqo'te)", EqpEntry.HeadShowEarMiqote ), + ( "Show Ears (Au Ra)", EqpEntry.HeadShowEarAuRa ), + ( "Show Ears (Viera)", EqpEntry.HeadShowEarViera ), + ( "Show on Hrothgar", EqpEntry.HeadShowHrothgarHat ), + ( "Show on Viera", EqpEntry.HeadShowVieraHat ), + ( "Unknown 46", EqpEntry._46 ), + ( "Unknown 54", EqpEntry._54 ), + ( "Unknown 55", EqpEntry._55 ), + ( "Unknown 58", EqpEntry._58 ), + ( "Unknown 59", EqpEntry._59 ), + ( "Unknown 60", EqpEntry._60 ), + ( "Unknown 61", EqpEntry._61 ), + ( "Unknown 62", EqpEntry._62 ), + ( "Unknown 63", EqpEntry._63 ), + }; + + private static readonly (string, EquipSlot)[] EqpEquipSlots = new[] + { + ( "Head", EquipSlot.Head ), + ( "Body", EquipSlot.Body ), + ( "Hands", EquipSlot.Hands ), + ( "Legs", EquipSlot.Legs ), + ( "Feet", EquipSlot.Feet ), + }; + + private static readonly (string, EquipSlot)[] EqdpEquipSlots = new[] + { + EqpEquipSlots[ 0 ], + EqpEquipSlots[ 1 ], + EqpEquipSlots[ 2 ], + EqpEquipSlots[ 3 ], + EqpEquipSlots[ 4 ], + ( "Ears", EquipSlot.Ears ), + ( "Neck", EquipSlot.Neck ), + ( "Wrist", EquipSlot.Wrists ), + ( "Left Finger", EquipSlot.RingL ), + ( "Right Finger", EquipSlot.RingR ), + }; + + private static readonly (string, Race)[] Races = new[] + { + ( "Midlander", Race.Midlander ), + ( "Highlander", Race.Highlander ), + ( "Elezen", Race.Elezen ), + ( "Miqo'te", Race.Miqote ), + ( "Roegadyn", Race.Roegadyn ), + ( "Lalafell", Race.Lalafell ), + ( "Au Ra", Race.AuRa ), + ( "Viera", Race.Viera ), + ( "Hrothgar", Race.Hrothgar ), + }; + + private static readonly (string, Gender)[] Genders = new[] + { + ( "Male", Gender.Male ), + ( "Female", Gender.Female ), + ( "Male (NPC)", Gender.MaleNpc ), + ( "Female (NPC)", Gender.FemaleNpc ), + }; + + private static readonly (string, ObjectType)[] ObjectTypes = new[] + { + ( "Equipment", ObjectType.Equipment ), + ( "Customization", ObjectType.Character ), + }; + + private static readonly (string, EquipSlot)[] EstEquipSlots = new[] + { + EqpEquipSlots[ 0 ], + EqpEquipSlots[ 1 ], + }; + + private static readonly (string, BodySlot)[] EstBodySlots = new[] + { + ( "Hair", BodySlot.Hair ), + ( "Face", BodySlot.Face ), + }; + + private static readonly (string, SubRace)[] Subraces = new[] + { + ( "Midlander", SubRace.Midlander ), + ( "Highlander", SubRace.Highlander ), + ( "Wildwood", SubRace.Wildwood ), + ( "Duskwright", SubRace.Duskwright ), + ( "Seeker Of The Sun", SubRace.SeekerOfTheSun ), + ( "Keeper Of The Moon", SubRace.KeeperOfTheMoon ), + ( "Seawolf", SubRace.Seawolf ), + ( "Hellsguard", SubRace.Hellsguard ), + ( "Plainsfolk", SubRace.Plainsfolk ), + ( "Dunesfolk", SubRace.Dunesfolk ), + ( "Raen", SubRace.Raen ), + ( "Xaela", SubRace.Xaela ), + ( "Rava", SubRace.Rava ), + ( "Veena", SubRace.Veena ), + ( "Hellion", SubRace.Hellion ), + ( "Lost", SubRace.Lost ), + }; + + private static readonly (string, RspAttribute)[] RspAttributes = new[] + { + ( "Male Minimum Size", RspAttribute.MaleMinSize ), + ( "Male Maximum Size", RspAttribute.MaleMaxSize ), + ( "Female Minimum Size", RspAttribute.FemaleMinSize ), + ( "Female Maximum Size", RspAttribute.FemaleMaxSize ), + ( "Bust Minimum X-Axis", RspAttribute.BustMinX ), + ( "Bust Maximum X-Axis", RspAttribute.BustMaxX ), + ( "Bust Minimum Y-Axis", RspAttribute.BustMinY ), + ( "Bust Maximum Y-Axis", RspAttribute.BustMaxY ), + ( "Bust Minimum Z-Axis", RspAttribute.BustMinZ ), + ( "Bust Maximum Z-Axis", RspAttribute.BustMaxZ ), + ( "Male Minimum Tail Length", RspAttribute.MaleMinTail ), + ( "Male Maximum Tail Length", RspAttribute.MaleMaxTail ), + ( "Female Minimum Tail Length", RspAttribute.FemaleMinTail ), + ( "Female Maximum Tail Length", RspAttribute.FemaleMaxTail ), + }; + + private static readonly (string, ObjectType)[] ImcObjectType = new[] + { + ObjectTypes[ 0 ], + ( "Weapon", ObjectType.Weapon ), + ( "Demihuman", ObjectType.DemiHuman ), + ( "Monster", ObjectType.Monster ), + }; + + private static readonly (string, BodySlot)[] ImcBodySlots = new[] + { + EstBodySlots[ 0 ], + EstBodySlots[ 1 ], + ( "Body", BodySlot.Body ), + ( "Tail", BodySlot.Tail ), + ( "Ears", BodySlot.Zear ), + }; + + private static bool PrintCheckBox( string name, ref bool value, bool def ) + { + var color = value == def ? 0 : value ? ColorDarkGreen : ColorDarkRed; + if( color == 0 ) + { + return ImGui.Checkbox( name, ref value ); + } + + ImGui.PushStyleColor( ImGuiCol.Text, color ); + var ret = ImGui.Checkbox( name, ref value ); + ImGui.PopStyleColor(); + return ret; + } + + private bool RestrictedInputInt( string name, ref ushort value, ushort min, ushort max ) + { + int tmp = value; + if( ImGui.InputInt( name, ref tmp, 0, 0, _editMode ? ImGuiInputTextFlags.EnterReturnsTrue : ImGuiInputTextFlags.ReadOnly ) + && tmp != value + && tmp >= min + && tmp <= max ) + { + value = ( ushort )tmp; + return true; + } + + return false; + } + + private static bool DefaultButton< T >( string name, ref T value, T defaultValue ) where T : IComparable< T > + { + var compare = defaultValue.CompareTo( value ); + var color = compare < 0 ? ColorDarkGreen : + compare > 0 ? ColorDarkRed : ImGui.ColorConvertFloat4ToU32( ImGui.GetStyle().Colors[ ( int )ImGuiCol.Button ] ); + + ImGui.PushStyleColor( ImGuiCol.Button, color ); + var ret = ImGui.Button( name, Vector2.UnitX * 120 ) && compare != 0; + ImGui.SameLine(); + ImGui.PopStyleColor(); + return ret; + } + + private bool DrawInputWithDefault( string name, ref ushort value, ushort defaultValue, ushort max ) + => DefaultButton( $"{( _editMode ? "Set to " : "" )}Default: {defaultValue}##imc{name}", ref value, defaultValue ) + || RestrictedInputInt( name, ref value, 0, max ); + + private static bool CustomCombo< T >( string label, IList< (string, T) > namesAndValues, out T value, ref int idx ) + { + value = idx < namesAndValues.Count ? namesAndValues[ idx ].Item2 : default!; + + if( !ImGui.BeginCombo( label, idx < namesAndValues.Count ? namesAndValues[ idx ].Item1 : string.Empty ) ) + { + return false; + } + + for( var i = 0; i < namesAndValues.Count; ++i ) + { + if( ImGui.Selectable( $"{namesAndValues[ i ].Item1}##{label}{i}", idx == i ) && idx != i ) + { + idx = i; + value = namesAndValues[ i ].Item2; + ImGui.EndCombo(); + return true; + } + } + + ImGui.EndCombo(); + return false; + } + + private bool DrawEqpRow( int manipIdx, IList< MetaManipulation > list ) + { + var ret = false; + var id = list[ manipIdx ].EqpIdentifier; + var val = list[ manipIdx ].EqpValue; + + if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) + { + var defaults = ( EqpEntry )Service< MetaDefaults >.Get().GetDefaultValue( list[ manipIdx ] )!; + var attributes = id.Slot switch + { + EquipSlot.Head => EqpAttributesHead, + EquipSlot.Body => EqpAttributesBody, + EquipSlot.Hands => EqpAttributesHands, + EquipSlot.Legs => EqpAttributesLegs, + EquipSlot.Feet => EqpAttributesFeet, + _ => Array.Empty< (string, EqpEntry) >(), + }; + + foreach( var (name, flag) in attributes ) + { + var tmp = val.HasFlag( flag ); + if( PrintCheckBox( $"{name}##manip", ref tmp, defaults.HasFlag( flag ) ) && _editMode && tmp != val.HasFlag( flag ) ) + { + list[ manipIdx ] = MetaManipulation.Eqp( id.Slot, id.SetId, tmp ? val | flag : val & ~flag ); + ret = true; + } + } + + ImGui.EndPopup(); + } + + ImGui.Text( ObjectType.Equipment.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.SetId.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.Slot.ToString() ); + return ret; + } + + private bool DrawGmpRow( int manipIdx, IList< MetaManipulation > list ) + { + var defaults = ( GmpEntry )Service< MetaDefaults >.Get().GetDefaultValue( list[ manipIdx ] )!; + var ret = false; + var id = list[ manipIdx ].GmpIdentifier; + var val = list[ manipIdx ].GmpValue; + + if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) + { + var enabled = val.Enabled; + var animated = val.Animated; + var rotationA = val.RotationA; + var rotationB = val.RotationB; + var rotationC = val.RotationC; + ushort unk = val.UnknownTotal; + + ret |= PrintCheckBox( "Visor Enabled##manip", ref enabled, defaults.Enabled ) && enabled != val.Enabled; + ret |= PrintCheckBox( "Visor Animated##manip", ref animated, defaults.Animated ); + ret |= DrawInputWithDefault( "Rotation A##manip", ref rotationA, defaults.RotationA, 0x3FF ); + ret |= DrawInputWithDefault( "Rotation B##manip", ref rotationB, defaults.RotationB, 0x3FF ); + ret |= DrawInputWithDefault( "Rotation C##manip", ref rotationC, defaults.RotationC, 0x3FF ); + ret |= DrawInputWithDefault( "Unknown Byte##manip", ref unk, defaults.UnknownTotal, 0xFF ); + + if( ret && _editMode ) + { + list[ manipIdx ] = MetaManipulation.Gmp( id.SetId, + new GmpEntry + { + Animated = animated, Enabled = enabled, UnknownTotal = ( byte )unk, + RotationA = rotationA, RotationB = rotationB, RotationC = rotationC, + } ); + } + + ImGui.EndPopup(); + } + + ImGui.Text( ObjectType.Equipment.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.SetId.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( EquipSlot.Head.ToString() ); + return ret; + } + + private static (bool, bool) GetEqdpBits( EquipSlot slot, EqdpEntry entry ) + { + return slot switch + { + EquipSlot.Head => ( entry.HasFlag( EqdpEntry.Head1 ), entry.HasFlag( EqdpEntry.Head2 ) ), + EquipSlot.Body => ( entry.HasFlag( EqdpEntry.Body1 ), entry.HasFlag( EqdpEntry.Body2 ) ), + EquipSlot.Hands => ( entry.HasFlag( EqdpEntry.Hands1 ), entry.HasFlag( EqdpEntry.Hands2 ) ), + EquipSlot.Legs => ( entry.HasFlag( EqdpEntry.Legs1 ), entry.HasFlag( EqdpEntry.Legs2 ) ), + EquipSlot.Feet => ( entry.HasFlag( EqdpEntry.Feet1 ), entry.HasFlag( EqdpEntry.Feet2 ) ), + EquipSlot.Neck => ( entry.HasFlag( EqdpEntry.Neck1 ), entry.HasFlag( EqdpEntry.Neck2 ) ), + EquipSlot.Ears => ( entry.HasFlag( EqdpEntry.Ears1 ), entry.HasFlag( EqdpEntry.Ears2 ) ), + EquipSlot.Wrists => ( entry.HasFlag( EqdpEntry.Wrists1 ), entry.HasFlag( EqdpEntry.Wrists2 ) ), + EquipSlot.RingR => ( entry.HasFlag( EqdpEntry.RingR1 ), entry.HasFlag( EqdpEntry.RingR2 ) ), + EquipSlot.RingL => ( entry.HasFlag( EqdpEntry.RingL1 ), entry.HasFlag( EqdpEntry.RingL2 ) ), + _ => ( false, false ), + }; + } + + private static EqdpEntry SetEqdpBits( EquipSlot slot, EqdpEntry value, bool bit1, bool bit2 ) + { + switch( slot ) + { + case EquipSlot.Head: + value = bit1 ? value | EqdpEntry.Head1 : value & ~EqdpEntry.Head1; + value = bit2 ? value | EqdpEntry.Head2 : value & ~EqdpEntry.Head2; + return value; + case EquipSlot.Body: + value = bit1 ? value | EqdpEntry.Body1 : value & ~EqdpEntry.Body1; + value = bit2 ? value | EqdpEntry.Body2 : value & ~EqdpEntry.Body2; + return value; + case EquipSlot.Hands: + value = bit1 ? value | EqdpEntry.Hands1 : value & ~EqdpEntry.Hands1; + value = bit2 ? value | EqdpEntry.Hands2 : value & ~EqdpEntry.Hands2; + return value; + case EquipSlot.Legs: + value = bit1 ? value | EqdpEntry.Legs1 : value & ~EqdpEntry.Legs1; + value = bit2 ? value | EqdpEntry.Legs2 : value & ~EqdpEntry.Legs2; + return value; + case EquipSlot.Feet: + value = bit1 ? value | EqdpEntry.Feet1 : value & ~EqdpEntry.Feet1; + value = bit2 ? value | EqdpEntry.Feet2 : value & ~EqdpEntry.Feet2; + return value; + case EquipSlot.Neck: + value = bit1 ? value | EqdpEntry.Neck1 : value & ~EqdpEntry.Neck1; + value = bit2 ? value | EqdpEntry.Neck2 : value & ~EqdpEntry.Neck2; + return value; + case EquipSlot.Ears: + value = bit1 ? value | EqdpEntry.Ears1 : value & ~EqdpEntry.Ears1; + value = bit2 ? value | EqdpEntry.Ears2 : value & ~EqdpEntry.Ears2; + return value; + case EquipSlot.Wrists: + value = bit1 ? value | EqdpEntry.Wrists1 : value & ~EqdpEntry.Wrists1; + value = bit2 ? value | EqdpEntry.Wrists2 : value & ~EqdpEntry.Wrists2; + return value; + case EquipSlot.RingR: + value = bit1 ? value | EqdpEntry.RingR1 : value & ~EqdpEntry.RingR1; + value = bit2 ? value | EqdpEntry.RingR2 : value & ~EqdpEntry.RingR2; + return value; + case EquipSlot.RingL: + value = bit1 ? value | EqdpEntry.RingL1 : value & ~EqdpEntry.RingL1; + value = bit2 ? value | EqdpEntry.RingL2 : value & ~EqdpEntry.RingL2; + return value; + } + + return value; + } + + private bool DrawEqdpRow( int manipIdx, IList< MetaManipulation > list ) + { + var defaults = ( EqdpEntry )Service< MetaDefaults >.Get().GetDefaultValue( list[ manipIdx ] )!; + var ret = false; + var id = list[ manipIdx ].EqdpIdentifier; + var val = list[ manipIdx ].EqdpValue; + + if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) + { + var (bit1, bit2) = GetEqdpBits( id.Slot, val ); + var (defBit1, defBit2) = GetEqdpBits( id.Slot, defaults ); + + ret |= PrintCheckBox( "Bit 1##manip", ref bit1, defBit1 ); + ret |= PrintCheckBox( "Bit 2##manip", ref bit2, defBit2 ); + + if( ret && _editMode ) + { + list[ manipIdx ] = MetaManipulation.Eqdp( id.Slot, id.GenderRace, id.SetId, SetEqdpBits( id.Slot, val, bit1, bit2 ) ); + } + + ImGui.EndPopup(); + } + + ImGui.Text( id.Slot.IsAccessory() + ? ObjectType.Accessory.ToString() + : ObjectType.Equipment.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.SetId.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.Slot.ToString() ); + ImGui.TableNextColumn(); + var (gender, race) = id.GenderRace.Split(); + ImGui.Text( race.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( gender.ToString() ); + return ret; + } + + private bool DrawEstRow( int manipIdx, IList< MetaManipulation > list ) + { + var defaults = ( ushort )Service< MetaDefaults >.Get().GetDefaultValue( list[ manipIdx ] )!; + var ret = false; + var id = list[ manipIdx ].EstIdentifier; + var val = list[ manipIdx ].EstValue; + if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) + { + if( DrawInputWithDefault( "No Idea what this does!##manip", ref val, defaults, ushort.MaxValue ) && _editMode ) + { + list[ manipIdx ] = new MetaManipulation( id.Value, val ); + ret = true; + } + + ImGui.EndPopup(); + } + + ImGui.Text( id.ObjectType.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.PrimaryId.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.ObjectType == ObjectType.Equipment + ? id.EquipSlot.ToString() + : id.BodySlot.ToString() ); + ImGui.TableNextColumn(); + var (gender, race) = id.GenderRace.Split(); + ImGui.Text( race.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( gender.ToString() ); + + return ret; + } + + private bool DrawImcRow( int manipIdx, IList< MetaManipulation > list ) + { + var defaults = ( ImcFile.ImageChangeData )Service< MetaDefaults >.Get().GetDefaultValue( list[ manipIdx ] )!; + var ret = false; + var id = list[ manipIdx ].ImcIdentifier; + var val = list[ manipIdx ].ImcValue; + + if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) + { + ushort materialId = val.MaterialId; + ushort vfxId = val.VfxId; + ushort decalId = val.DecalId; + var soundId = ( ushort )( val.SoundId >> 10 ); + var attributeMask = val.AttributeMask; + var materialAnimationId = ( ushort )( val.MaterialAnimationId >> 12 ); + ret |= DrawInputWithDefault( "Material Id", ref materialId, defaults.MaterialId, byte.MaxValue ); + ret |= DrawInputWithDefault( "Vfx Id", ref vfxId, defaults.VfxId, byte.MaxValue ); + ret |= DrawInputWithDefault( "Decal Id", ref decalId, defaults.DecalId, byte.MaxValue ); + ret |= DrawInputWithDefault( "Sound Id", ref soundId, defaults.SoundId, 0x3F ); + ret |= DrawInputWithDefault( "Attribute Mask", ref attributeMask, defaults.AttributeMask, 0x3FF ); + ret |= DrawInputWithDefault( "Material Animation Id", ref materialAnimationId, defaults.MaterialAnimationId, + byte.MaxValue ); + + if( ret && _editMode ) + { + var value = ImcExtensions.FromValues( ( byte )materialId, ( byte )decalId, attributeMask, ( byte )soundId, + ( byte )vfxId, ( byte )materialAnimationId ); + list[ manipIdx ] = new MetaManipulation( id.Value, value.ToInteger() ); + } + + ImGui.EndPopup(); + } + + ImGui.Text( id.ObjectType.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.PrimaryId.ToString() ); + ImGui.TableNextColumn(); + if( id.ObjectType == ObjectType.Accessory + || id.ObjectType == ObjectType.Equipment ) + { + ImGui.Text( id.ObjectType == ObjectType.Equipment + || id.ObjectType == ObjectType.Accessory + ? id.EquipSlot.ToString() + : id.BodySlot.ToString() ); + } + + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + if( id.ObjectType != ObjectType.Equipment + && id.ObjectType != ObjectType.Accessory ) + { + ImGui.Text( id.SecondaryId.ToString() ); + } + + ImGui.TableNextColumn(); + ImGui.Text( id.Variant.ToString() ); + return ret; + } + + private bool DrawRspRow( int manipIdx, IList< MetaManipulation > list ) + { + var defaults = ( float )Service< MetaDefaults >.Get().GetDefaultValue( list[ manipIdx ] )!; + var ret = false; + var id = list[ manipIdx ].RspIdentifier; + var val = list[ manipIdx ].RspValue; + + if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) + { + var color = defaults < val ? ColorDarkGreen : + defaults > val ? ColorDarkRed : ImGui.ColorConvertFloat4ToU32( ImGui.GetStyle().Colors[ ( int )ImGuiCol.Button ] ); + + if( DefaultButton( + $"{( _editMode ? "Set to " : "" )}Default: {defaults:F3}##scaleManip", ref val, defaults ) + && _editMode ) + { + list[ manipIdx ] = MetaManipulation.Rsp( id.SubRace, id.Attribute, defaults ); + ret = true; + } + + ImGui.SetNextItemWidth( 50 ); + if( ImGui.InputFloat( "Scale###manip", ref val, 0, 0, "%.3f", + _editMode ? ImGuiInputTextFlags.EnterReturnsTrue : ImGuiInputTextFlags.ReadOnly ) + && val >= 0 + && val <= 5 + && _editMode ) + { + list[ manipIdx ] = MetaManipulation.Rsp( id.SubRace, id.Attribute, val ); + ret = true; + } + + ImGui.EndPopup(); + } + + ImGui.Text( id.Attribute.ToUngenderedString() ); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.Text( id.SubRace.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.Attribute.ToGender().ToString() ); + return ret; + } + + private bool DrawManipulationRow( ref int manipIdx, IList< MetaManipulation > list ) + { + var type = list[ manipIdx ].Type; + + if( _editMode ) + { + ImGui.TableNextColumn(); + ImGui.PushFont( UiBuilder.IconFont ); + if( ImGui.Button( $"{FontAwesomeIcon.Trash.ToIconString()}##manipDelete{manipIdx}" ) ) + { + list.RemoveAt( manipIdx ); + ImGui.PopFont(); + ImGui.TableNextRow(); + --manipIdx; + return true; + } + + ImGui.PopFont(); + } + + ImGui.TableNextColumn(); + ImGui.Text( type.ToString() ); + ImGui.TableNextColumn(); + + var changes = false; + switch( type ) + { + case MetaType.Eqp: + changes = DrawEqpRow( manipIdx, list ); + break; + case MetaType.Gmp: + changes = DrawGmpRow( manipIdx, list ); + break; + case MetaType.Eqdp: + changes = DrawEqdpRow( manipIdx, list ); + break; + case MetaType.Est: + changes = DrawEstRow( manipIdx, list ); + break; + case MetaType.Imc: + changes = DrawImcRow( manipIdx, list ); + break; + case MetaType.Rsp: + changes = DrawRspRow( manipIdx, list ); + break; + } + + ImGui.TableSetColumnIndex( 9 ); + if( ImGui.Selectable( $"{list[ manipIdx ].Value}##{manipIdx}" ) ) + { + ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); + } + + ImGui.TableNextRow(); + return changes; + } + + + private MetaType DrawNewTypeSelection() + { + ImGui.RadioButton( "IMC##newManipType", ref _newManipTypeIdx, 1 ); + ImGui.SameLine(); + ImGui.RadioButton( "EQDP##newManipType", ref _newManipTypeIdx, 2 ); + ImGui.SameLine(); + ImGui.RadioButton( "EQP##newManipType", ref _newManipTypeIdx, 3 ); + ImGui.SameLine(); + ImGui.RadioButton( "EST##newManipType", ref _newManipTypeIdx, 4 ); + ImGui.SameLine(); + ImGui.RadioButton( "GMP##newManipType", ref _newManipTypeIdx, 5 ); + ImGui.SameLine(); + ImGui.RadioButton( "RSP##newManipType", ref _newManipTypeIdx, 6 ); + return ( MetaType )_newManipTypeIdx; + } + + private bool DrawNewManipulationPopup( string popupName, IList< MetaManipulation > list ) + { + var change = false; + if( ImGui.BeginPopup( popupName ) ) + { + var manipType = DrawNewTypeSelection(); + MetaManipulation? newManip = null; + switch( manipType ) + { + case MetaType.Imc: + { + RestrictedInputInt( "Set Id##newManipImc", ref _newManipSetId, 0, ushort.MaxValue ); + RestrictedInputInt( "Variant##newManipImc", ref _newManipVariant, 0, byte.MaxValue ); + CustomCombo( "Object Type", ImcObjectType, out var objectType, ref _newManipObjectType ); + EquipSlot equipSlot = default; + switch( objectType ) + { + case ObjectType.Equipment: + CustomCombo( "Equipment Slot", EqdpEquipSlots, out equipSlot, ref _newManipEquipSlot ); + newManip = MetaManipulation.Imc( equipSlot, _newManipSetId, _newManipVariant, + new ImcFile.ImageChangeData() ); + break; + case ObjectType.DemiHuman: + case ObjectType.Weapon: + case ObjectType.Monster: + RestrictedInputInt( "Secondary Id##newManipImc", ref _newManipSecondaryId, 0, ushort.MaxValue ); + CustomCombo( "Body Slot", ImcBodySlots, out var bodySlot, ref _newManipBodySlot ); + newManip = MetaManipulation.Imc( objectType, bodySlot, _newManipSetId, _newManipSecondaryId, + _newManipVariant, new ImcFile.ImageChangeData() ); + break; + } + + break; + } + case MetaType.Eqdp: + { + RestrictedInputInt( "Set Id##newManipEqdp", ref _newManipSetId, 0, ushort.MaxValue ); + CustomCombo( "Equipment Slot", EqdpEquipSlots, out var equipSlot, ref _newManipEquipSlot ); + CustomCombo( "Race", Races, out var race, ref _newManipRace ); + CustomCombo( "Gender", Genders, out var gender, ref _newManipGender ); + newManip = MetaManipulation.Eqdp( equipSlot, GameData.CombinedRace( gender, race ), ( ushort )_newManipSetId, + new EqdpEntry() ); + break; + } + case MetaType.Eqp: + { + RestrictedInputInt( "Set Id##newManipEqp", ref _newManipSetId, 0, ushort.MaxValue ); + CustomCombo( "Equipment Slot", EqpEquipSlots, out var equipSlot, ref _newManipEquipSlot ); + newManip = MetaManipulation.Eqp( equipSlot, ( ushort )_newManipSetId, 0 ); + break; + } + case MetaType.Est: + { + RestrictedInputInt( "Set Id##newManipEst", ref _newManipSetId, 0, ushort.MaxValue ); + CustomCombo( "Object Type", ObjectTypes, out var objectType, ref _newManipObjectType ); + EquipSlot equipSlot = default; + BodySlot bodySlot = default; + switch( ( ObjectType )_newManipObjectType ) + { + case ObjectType.Equipment: + CustomCombo( "Equipment Slot", EstEquipSlots, out equipSlot, ref _newManipEquipSlot ); + break; + case ObjectType.Character: + CustomCombo( "Body Slot", EstBodySlots, out bodySlot, ref _newManipBodySlot ); + break; + } + + CustomCombo( "Race", Races, out var race, ref _newManipRace ); + CustomCombo( "Gender", Genders, out var gender, ref _newManipGender ); + newManip = MetaManipulation.Est( objectType, equipSlot, GameData.CombinedRace( gender, race ), bodySlot, + ( ushort )_newManipSetId, 0 ); + break; + } + case MetaType.Gmp: + RestrictedInputInt( "Set Id##newManipGmp", ref _newManipSetId, 0, ushort.MaxValue ); + newManip = MetaManipulation.Gmp( ( ushort )_newManipSetId, new GmpEntry() ); + break; + case MetaType.Rsp: + CustomCombo( "Subrace", Subraces, out var subRace, ref _newManipSubrace ); + CustomCombo( "Attribute", RspAttributes, out var rspAttribute, ref _newManipAttribute ); + newManip = MetaManipulation.Rsp( subRace, rspAttribute, 1f ); + break; + } + + if( ImGui.Button( "Create Manipulation##newManip", Vector2.UnitX * -1 ) + && newManip != null + && list.All( m => m.Identifier != newManip.Value.Identifier ) ) + { + var def = Service< MetaDefaults >.Get().GetDefaultValue( newManip.Value ); + if( def != null ) + { + var manip = newManip.Value.Type switch + { + MetaType.Est => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ), + MetaType.Eqp => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ), + MetaType.Eqdp => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ), + MetaType.Gmp => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ), + MetaType.Imc => new MetaManipulation( newManip.Value.Identifier, + ( ( ImcFile.ImageChangeData )def ).ToInteger() ), + MetaType.Rsp => MetaManipulation.Rsp( newManip.Value.RspIdentifier.SubRace, + newManip.Value.RspIdentifier.Attribute, ( float )def ), + _ => throw new InvalidEnumArgumentException(), + }; + list.Add( manip ); + change = true; + } + } + + ImGui.EndPopup(); + } + + return change; + } + + private bool DrawMetaManipulationsTable( string label, IList< MetaManipulation > list ) + { + var numRows = _editMode ? 11 : 10; + var changes = false; + if( list.Count > 0 + && ImGui.BeginTable( label, numRows, + ImGuiTableFlags.BordersInner | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit ) ) + { + if( _editMode ) + { + ImGui.TableNextColumn(); + } + + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Type##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Object Type##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Set##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Slot##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Race##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Gender##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Secondary ID##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Variant##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Value##{label}" ); + ImGui.TableNextRow(); + + for( var i = 0; i < list.Count; ++i ) + { + changes |= DrawManipulationRow( ref i, list ); + } + + ImGui.EndTable(); + } + + var popupName = $"##newManip{label}"; + if( _editMode ) + { + changes |= DrawNewManipulationPopup( $"##newManip{label}", list ); + if( ImGui.Button( $"Add New Manipulation##{label}", Vector2.UnitX * -1 ) ) + { + ImGui.OpenPopup( popupName ); + } + + return changes; + } + + return false; + } + } + } +} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs index 4c659b51..ae79c5b4 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs @@ -430,7 +430,7 @@ namespace Penumbra.UI { if( ImGui.Button( ButtonReloadJson ) ) { - _selector.ReloadCurrentMod(); + _selector.ReloadCurrentMod( true, false ); } if( ImGui.IsItemHovered() ) @@ -443,13 +443,13 @@ namespace Penumbra.UI { if( ImGui.Button( "Recompute Metadata" ) ) { - _selector.ReloadCurrentMod( true ); + _selector.ReloadCurrentMod( true, true ); } if( ImGui.IsItemHovered() ) { ImGui.SetTooltip( - "Force a recomputation of the metadata_manipulations.json file from all .meta files in the folder.\nAlso reloads the mod." ); + "Force a recomputation of the metadata_manipulations.json file from all .meta files in the folder.\nAlso reloads the mod.\nBe aware that this removes all manually added metadata changes." ); } } diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs index b06a9c84..e1b635d3 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs @@ -544,14 +544,14 @@ namespace Penumbra.UI SetSelection( idx ); } - public void ReloadCurrentMod( bool recomputeMeta = false ) + public void ReloadCurrentMod( bool reloadMeta = false, bool recomputeMeta = false ) { if( Mod == null ) { return; } - if( _index >= 0 && _modManager.UpdateMod( Mod.Data, recomputeMeta ) ) + if( _index >= 0 && _modManager.UpdateMod( Mod.Data, reloadMeta, recomputeMeta ) ) { ResetModNamesLower(); SelectModByDir( Mod.Data.BasePath.Name );