mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Stop using windows forms, add extensive meta manipulation editing, fix a concurrency crash and a dumb crash.
This commit is contained in:
parent
e5b739fc52
commit
0c3c7ea363
25 changed files with 1266 additions and 302 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
||||||
Subproject commit cbc29200e8b80d264c8a326cdc62e841e12d1c53
|
Subproject commit 732c9a3bd7c967ca427e24f4b8df65f722fe72d2
|
||||||
|
|
@ -1,43 +1,42 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace Penumbra.GameData.Enums
|
namespace Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
public enum BodySlot : byte
|
||||||
{
|
{
|
||||||
public enum BodySlot : byte
|
Unknown,
|
||||||
{
|
Hair,
|
||||||
Unknown,
|
Face,
|
||||||
Hair,
|
Tail,
|
||||||
Face,
|
Body,
|
||||||
Tail,
|
Zear,
|
||||||
Body,
|
}
|
||||||
Zear,
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class BodySlotEnumExtension
|
public static class BodySlotEnumExtension
|
||||||
|
{
|
||||||
|
public static string ToSuffix( this BodySlot value )
|
||||||
{
|
{
|
||||||
public static string ToSuffix( this BodySlot value )
|
return value switch
|
||||||
{
|
{
|
||||||
return value switch
|
BodySlot.Zear => "zear",
|
||||||
{
|
BodySlot.Face => "face",
|
||||||
BodySlot.Zear => "zear",
|
BodySlot.Hair => "hair",
|
||||||
BodySlot.Face => "face",
|
BodySlot.Body => "body",
|
||||||
BodySlot.Hair => "hair",
|
BodySlot.Tail => "tail",
|
||||||
BodySlot.Body => "body",
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
BodySlot.Tail => "tail",
|
|
||||||
_ => throw new InvalidEnumArgumentException(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static partial class Names
|
|
||||||
{
|
|
||||||
public static readonly Dictionary< string, BodySlot > StringToBodySlot = new()
|
|
||||||
{
|
|
||||||
{ BodySlot.Zear.ToSuffix(), BodySlot.Zear },
|
|
||||||
{ BodySlot.Face.ToSuffix(), BodySlot.Face },
|
|
||||||
{ BodySlot.Hair.ToSuffix(), BodySlot.Hair },
|
|
||||||
{ BodySlot.Body.ToSuffix(), BodySlot.Body },
|
|
||||||
{ BodySlot.Tail.ToSuffix(), BodySlot.Tail },
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static partial class Names
|
||||||
|
{
|
||||||
|
public static readonly Dictionary< string, BodySlot > StringToBodySlot = new()
|
||||||
|
{
|
||||||
|
{ BodySlot.Zear.ToSuffix(), BodySlot.Zear },
|
||||||
|
{ BodySlot.Face.ToSuffix(), BodySlot.Face },
|
||||||
|
{ BodySlot.Hair.ToSuffix(), BodySlot.Hair },
|
||||||
|
{ BodySlot.Body.ToSuffix(), BodySlot.Body },
|
||||||
|
{ BodySlot.Tail.ToSuffix(), BodySlot.Tail },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Penumbra.GameData.Enums
|
namespace Penumbra.GameData.Enums
|
||||||
{
|
{
|
||||||
|
|
@ -30,7 +32,7 @@ namespace Penumbra.GameData.Enums
|
||||||
All = 22, // Not officially existing
|
All = 22, // Not officially existing
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EquipSlotEnumExtension
|
public static class EquipSlotExtensions
|
||||||
{
|
{
|
||||||
public static string ToSuffix( this EquipSlot value )
|
public static string ToSuffix( this EquipSlot value )
|
||||||
{
|
{
|
||||||
|
|
@ -79,6 +81,36 @@ namespace Penumbra.GameData.Enums
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ToName( this EquipSlot value )
|
||||||
|
{
|
||||||
|
return value switch
|
||||||
|
{
|
||||||
|
EquipSlot.Head => "Head",
|
||||||
|
EquipSlot.Hands => "Hands",
|
||||||
|
EquipSlot.Legs => "Legs",
|
||||||
|
EquipSlot.Feet => "Feet",
|
||||||
|
EquipSlot.Body => "Body",
|
||||||
|
EquipSlot.Ears => "Earrings",
|
||||||
|
EquipSlot.Neck => "Necklace",
|
||||||
|
EquipSlot.RFinger => "Right Ring",
|
||||||
|
EquipSlot.LFinger => "Left Ring",
|
||||||
|
EquipSlot.Wrists => "Bracelets",
|
||||||
|
EquipSlot.MainHand => "Primary Weapon",
|
||||||
|
EquipSlot.OffHand => "Secondary Weapon",
|
||||||
|
EquipSlot.Belt => "Belt",
|
||||||
|
EquipSlot.BothHand => "Primary Weapon",
|
||||||
|
EquipSlot.HeadBody => "Head and Body",
|
||||||
|
EquipSlot.BodyHandsLegsFeet => "Costume",
|
||||||
|
EquipSlot.SoulCrystal => "Soul Crystal",
|
||||||
|
EquipSlot.LegsFeet => "Bottom",
|
||||||
|
EquipSlot.FullBody => "Costume",
|
||||||
|
EquipSlot.BodyHands => "Top",
|
||||||
|
EquipSlot.BodyLegsFeet => "Costume",
|
||||||
|
EquipSlot.All => "Costume",
|
||||||
|
_ => "Unknown",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsEquipment( this EquipSlot value )
|
public static bool IsEquipment( this EquipSlot value )
|
||||||
{
|
{
|
||||||
return value switch
|
return value switch
|
||||||
|
|
@ -104,6 +136,10 @@ namespace Penumbra.GameData.Enums
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly EquipSlot[] EquipmentSlots = Enum.GetValues< EquipSlot >().Where( e => e.IsEquipment() ).ToArray();
|
||||||
|
public static readonly EquipSlot[] AccessorySlots = Enum.GetValues< EquipSlot >().Where( e => e.IsAccessory() ).ToArray();
|
||||||
|
public static readonly EquipSlot[] EqdpSlots = EquipmentSlots.Concat( AccessorySlots ).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static partial class Names
|
public static partial class Names
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,55 @@
|
||||||
namespace Penumbra.GameData.Enums
|
using System;
|
||||||
|
|
||||||
|
namespace Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
public enum ObjectType : byte
|
||||||
{
|
{
|
||||||
public enum ObjectType : byte
|
Unknown,
|
||||||
{
|
Vfx,
|
||||||
Unknown,
|
DemiHuman,
|
||||||
Vfx,
|
Accessory,
|
||||||
DemiHuman,
|
World,
|
||||||
Accessory,
|
Housing,
|
||||||
World,
|
Monster,
|
||||||
Housing,
|
Icon,
|
||||||
Monster,
|
LoadingScreen,
|
||||||
Icon,
|
Map,
|
||||||
LoadingScreen,
|
Interface,
|
||||||
Map,
|
Equipment,
|
||||||
Interface,
|
Character,
|
||||||
Equipment,
|
Weapon,
|
||||||
Character,
|
Font,
|
||||||
Weapon,
|
}
|
||||||
Font,
|
|
||||||
}
|
public static class ObjectTypeExtensions
|
||||||
|
{
|
||||||
|
public static string ToName( this ObjectType type )
|
||||||
|
=> type switch
|
||||||
|
{
|
||||||
|
ObjectType.Vfx => "Visual Effect",
|
||||||
|
ObjectType.DemiHuman => "Demi Human",
|
||||||
|
ObjectType.Accessory => "Accessory",
|
||||||
|
ObjectType.World => "Doodad",
|
||||||
|
ObjectType.Housing => "Housing Object",
|
||||||
|
ObjectType.Monster => "Monster",
|
||||||
|
ObjectType.Icon => "Icon",
|
||||||
|
ObjectType.LoadingScreen => "Loading Screen",
|
||||||
|
ObjectType.Map => "Map",
|
||||||
|
ObjectType.Interface => "UI Element",
|
||||||
|
ObjectType.Equipment => "Equipment",
|
||||||
|
ObjectType.Character => "Character",
|
||||||
|
ObjectType.Weapon => "Weapon",
|
||||||
|
ObjectType.Font => "Font",
|
||||||
|
_ => "Unknown",
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public static readonly ObjectType[] ValidImcTypes =
|
||||||
|
{
|
||||||
|
ObjectType.Equipment,
|
||||||
|
ObjectType.Accessory,
|
||||||
|
ObjectType.DemiHuman,
|
||||||
|
ObjectType.Monster,
|
||||||
|
ObjectType.Weapon,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -2,106 +2,119 @@ using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
namespace Penumbra.GameData.Structs
|
namespace Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum EqdpEntry : ushort
|
||||||
{
|
{
|
||||||
[Flags]
|
Invalid = 0,
|
||||||
public enum EqdpEntry : ushort
|
Head1 = 0b0000000001,
|
||||||
|
Head2 = 0b0000000010,
|
||||||
|
HeadMask = 0b0000000011,
|
||||||
|
|
||||||
|
Body1 = 0b0000000100,
|
||||||
|
Body2 = 0b0000001000,
|
||||||
|
BodyMask = 0b0000001100,
|
||||||
|
|
||||||
|
Hands1 = 0b0000010000,
|
||||||
|
Hands2 = 0b0000100000,
|
||||||
|
HandsMask = 0b0000110000,
|
||||||
|
|
||||||
|
Legs1 = 0b0001000000,
|
||||||
|
Legs2 = 0b0010000000,
|
||||||
|
LegsMask = 0b0011000000,
|
||||||
|
|
||||||
|
Feet1 = 0b0100000000,
|
||||||
|
Feet2 = 0b1000000000,
|
||||||
|
FeetMask = 0b1100000000,
|
||||||
|
|
||||||
|
Ears1 = 0b0000000001,
|
||||||
|
Ears2 = 0b0000000010,
|
||||||
|
EarsMask = 0b0000000011,
|
||||||
|
|
||||||
|
Neck1 = 0b0000000100,
|
||||||
|
Neck2 = 0b0000001000,
|
||||||
|
NeckMask = 0b0000001100,
|
||||||
|
|
||||||
|
Wrists1 = 0b0000010000,
|
||||||
|
Wrists2 = 0b0000100000,
|
||||||
|
WristsMask = 0b0000110000,
|
||||||
|
|
||||||
|
RingR1 = 0b0001000000,
|
||||||
|
RingR2 = 0b0010000000,
|
||||||
|
RingRMask = 0b0011000000,
|
||||||
|
|
||||||
|
RingL1 = 0b0100000000,
|
||||||
|
RingL2 = 0b1000000000,
|
||||||
|
RingLMask = 0b1100000000,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Eqdp
|
||||||
|
{
|
||||||
|
public static int Offset( EquipSlot slot )
|
||||||
|
=> slot switch
|
||||||
|
{
|
||||||
|
EquipSlot.Head => 0,
|
||||||
|
EquipSlot.Body => 2,
|
||||||
|
EquipSlot.Hands => 4,
|
||||||
|
EquipSlot.Legs => 6,
|
||||||
|
EquipSlot.Feet => 8,
|
||||||
|
EquipSlot.Ears => 0,
|
||||||
|
EquipSlot.Neck => 2,
|
||||||
|
EquipSlot.Wrists => 4,
|
||||||
|
EquipSlot.RFinger => 6,
|
||||||
|
EquipSlot.LFinger => 8,
|
||||||
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static (bool, bool) ToBits( this EqdpEntry entry, EquipSlot slot )
|
||||||
|
=> 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.Ears => ( entry.HasFlag( EqdpEntry.Ears1 ), entry.HasFlag( EqdpEntry.Ears2 ) ),
|
||||||
|
EquipSlot.Neck => ( entry.HasFlag( EqdpEntry.Neck1 ), entry.HasFlag( EqdpEntry.Neck2 ) ),
|
||||||
|
EquipSlot.Wrists => ( entry.HasFlag( EqdpEntry.Wrists1 ), entry.HasFlag( EqdpEntry.Wrists2 ) ),
|
||||||
|
EquipSlot.RFinger => ( entry.HasFlag( EqdpEntry.RingR1 ), entry.HasFlag( EqdpEntry.RingR2 ) ),
|
||||||
|
EquipSlot.LFinger => ( entry.HasFlag( EqdpEntry.RingL1 ), entry.HasFlag( EqdpEntry.RingL2 ) ),
|
||||||
|
_ => throw new InvalidEnumArgumentException(),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static EqdpEntry FromSlotAndBits( EquipSlot slot, bool bit1, bool bit2 )
|
||||||
{
|
{
|
||||||
Invalid = 0,
|
EqdpEntry ret = 0;
|
||||||
Head1 = 0b0000000001,
|
var offset = Offset( slot );
|
||||||
Head2 = 0b0000000010,
|
if( bit1 )
|
||||||
HeadMask = 0b0000000011,
|
{
|
||||||
|
ret |= ( EqdpEntry )( 1 << offset );
|
||||||
|
}
|
||||||
|
|
||||||
Body1 = 0b0000000100,
|
if( bit2 )
|
||||||
Body2 = 0b0000001000,
|
{
|
||||||
BodyMask = 0b0000001100,
|
ret |= ( EqdpEntry )( 1 << ( offset + 1 ) );
|
||||||
|
}
|
||||||
|
|
||||||
Hands1 = 0b0000010000,
|
return ret;
|
||||||
Hands2 = 0b0000100000,
|
|
||||||
HandsMask = 0b0000110000,
|
|
||||||
|
|
||||||
Legs1 = 0b0001000000,
|
|
||||||
Legs2 = 0b0010000000,
|
|
||||||
LegsMask = 0b0011000000,
|
|
||||||
|
|
||||||
Feet1 = 0b0100000000,
|
|
||||||
Feet2 = 0b1000000000,
|
|
||||||
FeetMask = 0b1100000000,
|
|
||||||
|
|
||||||
Ears1 = 0b0000000001,
|
|
||||||
Ears2 = 0b0000000010,
|
|
||||||
EarsMask = 0b0000000011,
|
|
||||||
|
|
||||||
Neck1 = 0b0000000100,
|
|
||||||
Neck2 = 0b0000001000,
|
|
||||||
NeckMask = 0b0000001100,
|
|
||||||
|
|
||||||
Wrists1 = 0b0000010000,
|
|
||||||
Wrists2 = 0b0000100000,
|
|
||||||
WristsMask = 0b0000110000,
|
|
||||||
|
|
||||||
RingR1 = 0b0001000000,
|
|
||||||
RingR2 = 0b0010000000,
|
|
||||||
RingRMask = 0b0011000000,
|
|
||||||
|
|
||||||
RingL1 = 0b0100000000,
|
|
||||||
RingL2 = 0b1000000000,
|
|
||||||
RingLMask = 0b1100000000,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Eqdp
|
public static EqdpEntry Mask( EquipSlot slot )
|
||||||
{
|
{
|
||||||
public static int Offset( EquipSlot slot )
|
return slot switch
|
||||||
{
|
{
|
||||||
return slot switch
|
EquipSlot.Head => EqdpEntry.HeadMask,
|
||||||
{
|
EquipSlot.Body => EqdpEntry.BodyMask,
|
||||||
EquipSlot.Head => 0,
|
EquipSlot.Hands => EqdpEntry.HandsMask,
|
||||||
EquipSlot.Body => 2,
|
EquipSlot.Legs => EqdpEntry.LegsMask,
|
||||||
EquipSlot.Hands => 4,
|
EquipSlot.Feet => EqdpEntry.FeetMask,
|
||||||
EquipSlot.Legs => 6,
|
EquipSlot.Ears => EqdpEntry.EarsMask,
|
||||||
EquipSlot.Feet => 8,
|
EquipSlot.Neck => EqdpEntry.NeckMask,
|
||||||
EquipSlot.Ears => 0,
|
EquipSlot.Wrists => EqdpEntry.WristsMask,
|
||||||
EquipSlot.Neck => 2,
|
EquipSlot.RFinger => EqdpEntry.RingRMask,
|
||||||
EquipSlot.Wrists => 4,
|
EquipSlot.LFinger => EqdpEntry.RingLMask,
|
||||||
EquipSlot.RFinger => 6,
|
_ => 0,
|
||||||
EquipSlot.LFinger => 8,
|
};
|
||||||
_ => throw new InvalidEnumArgumentException(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static EqdpEntry FromSlotAndBits( EquipSlot slot, bool bit1, bool bit2 )
|
|
||||||
{
|
|
||||||
EqdpEntry ret = 0;
|
|
||||||
var offset = Offset( slot );
|
|
||||||
if( bit1 )
|
|
||||||
{
|
|
||||||
ret |= ( EqdpEntry )( 1 << offset );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( bit2 )
|
|
||||||
{
|
|
||||||
ret |= ( EqdpEntry )( 1 << ( offset + 1 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static EqdpEntry Mask( EquipSlot slot )
|
|
||||||
{
|
|
||||||
return slot switch
|
|
||||||
{
|
|
||||||
EquipSlot.Head => EqdpEntry.HeadMask,
|
|
||||||
EquipSlot.Body => EqdpEntry.BodyMask,
|
|
||||||
EquipSlot.Hands => EqdpEntry.HandsMask,
|
|
||||||
EquipSlot.Legs => EqdpEntry.LegsMask,
|
|
||||||
EquipSlot.Feet => EqdpEntry.FeetMask,
|
|
||||||
EquipSlot.Ears => EqdpEntry.EarsMask,
|
|
||||||
EquipSlot.Neck => EqdpEntry.NeckMask,
|
|
||||||
EquipSlot.Wrists => EqdpEntry.WristsMask,
|
|
||||||
EquipSlot.RFinger => EqdpEntry.RingRMask,
|
|
||||||
EquipSlot.LFinger => EqdpEntry.RingLMask,
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -198,12 +198,14 @@ public static class Eqp
|
||||||
EqpEntry._55 => EquipSlot.Head,
|
EqpEntry._55 => EquipSlot.Head,
|
||||||
EqpEntry.HeadShowHrothgarHat => EquipSlot.Head,
|
EqpEntry.HeadShowHrothgarHat => EquipSlot.Head,
|
||||||
EqpEntry.HeadShowVieraHat => EquipSlot.Head,
|
EqpEntry.HeadShowVieraHat => EquipSlot.Head,
|
||||||
EqpEntry._58 => EquipSlot.Head,
|
|
||||||
EqpEntry._59 => EquipSlot.Head,
|
// Currently unused.
|
||||||
EqpEntry._60 => EquipSlot.Head,
|
EqpEntry._58 => EquipSlot.Unknown,
|
||||||
EqpEntry._61 => EquipSlot.Head,
|
EqpEntry._59 => EquipSlot.Unknown,
|
||||||
EqpEntry._62 => EquipSlot.Head,
|
EqpEntry._60 => EquipSlot.Unknown,
|
||||||
EqpEntry._63 => EquipSlot.Head,
|
EqpEntry._61 => EquipSlot.Unknown,
|
||||||
|
EqpEntry._62 => EquipSlot.Unknown,
|
||||||
|
EqpEntry._63 => EquipSlot.Unknown,
|
||||||
|
|
||||||
_ => EquipSlot.Unknown,
|
_ => EquipSlot.Unknown,
|
||||||
};
|
};
|
||||||
|
|
@ -299,7 +301,7 @@ public static class Eqp
|
||||||
public static readonly EqpEntry[] EqpAttributesFeet = GetEntriesForSlot( EquipSlot.Feet );
|
public static readonly EqpEntry[] EqpAttributesFeet = GetEntriesForSlot( EquipSlot.Feet );
|
||||||
public static readonly EqpEntry[] EqpAttributesHead = GetEntriesForSlot( EquipSlot.Head );
|
public static readonly EqpEntry[] EqpAttributesHead = GetEntriesForSlot( EquipSlot.Head );
|
||||||
|
|
||||||
public static IReadOnlyDictionary< EquipSlot, EqpEntry[] > EqpAttributes = new Dictionary< EquipSlot, EqpEntry[] >()
|
public static readonly IReadOnlyDictionary< EquipSlot, EqpEntry[] > EqpAttributes = new Dictionary< EquipSlot, EqpEntry[] >()
|
||||||
{
|
{
|
||||||
[ EquipSlot.Body ] = EqpAttributesBody,
|
[ EquipSlot.Body ] = EqpAttributesBody,
|
||||||
[ EquipSlot.Legs ] = EqpAttributesLegs,
|
[ EquipSlot.Legs ] = EqpAttributesLegs,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public unsafe partial class PathResolver
|
||||||
// and use the last game object that called EnableDraw to link them.
|
// and use the last game object that called EnableDraw to link them.
|
||||||
public delegate IntPtr CharacterBaseCreateDelegate( uint a, IntPtr b, IntPtr c, byte d );
|
public delegate IntPtr CharacterBaseCreateDelegate( uint a, IntPtr b, IntPtr c, byte d );
|
||||||
|
|
||||||
[Signature( "E8 ?? ?? ?? ?? 48 85 C0 74 21 C7 40" )]
|
[Signature( "E8 ?? ?? ?? ?? 48 85 C0 74 21 C7 40", DetourName = "CharacterBaseCreateDetour" )]
|
||||||
public Hook< CharacterBaseCreateDelegate >? CharacterBaseCreateHook;
|
public Hook< CharacterBaseCreateDelegate >? CharacterBaseCreateHook;
|
||||||
|
|
||||||
private ModCollection? _lastCreatedCollection;
|
private ModCollection? _lastCreatedCollection;
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ public unsafe struct CharacterUtility
|
||||||
1404 => EqdpStartIdx + 25,
|
1404 => EqdpStartIdx + 25,
|
||||||
9104 => EqdpStartIdx + 26,
|
9104 => EqdpStartIdx + 26,
|
||||||
9204 => EqdpStartIdx + 27,
|
9204 => EqdpStartIdx + 27,
|
||||||
_ => throw new ArgumentException(),
|
_ => -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
[FieldOffset( 0 )]
|
[FieldOffset( 0 )]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows.Forms;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,21 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Memory;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
using Penumbra.Interop;
|
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Files;
|
namespace Penumbra.Meta.Files;
|
||||||
|
|
||||||
public readonly struct ImcEntry : IEquatable< ImcEntry >
|
public readonly struct ImcEntry : IEquatable< ImcEntry >
|
||||||
{
|
{
|
||||||
public readonly byte MaterialId;
|
public byte MaterialId { get; init; }
|
||||||
public readonly byte DecalId;
|
public byte DecalId { get; init; }
|
||||||
private readonly ushort _attributeAndSound;
|
private readonly ushort _attributeAndSound;
|
||||||
public readonly byte VfxId;
|
public byte VfxId { get; init; }
|
||||||
public readonly byte MaterialAnimationId;
|
public byte MaterialAnimationId { get; init; }
|
||||||
|
|
||||||
public ushort AttributeMask
|
public ushort AttributeMask
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,18 @@ 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 readonly EqdpEntry Entry;
|
public EqdpEntry Entry { get; init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter( typeof( StringEnumConverter ) )]
|
||||||
public readonly Gender Gender;
|
public Gender Gender { get; init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter( typeof( StringEnumConverter ) )]
|
||||||
public readonly ModelRace Race;
|
public ModelRace Race { get; init; }
|
||||||
public readonly ushort SetId;
|
|
||||||
|
public ushort SetId { get; init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter( typeof( StringEnumConverter ) )]
|
||||||
public readonly EquipSlot Slot;
|
public EquipSlot Slot { get; init; }
|
||||||
|
|
||||||
public EqdpManipulation( EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, ushort setId )
|
public EqdpManipulation( EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, ushort setId )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,12 @@ namespace Penumbra.Meta.Manipulations;
|
||||||
public readonly struct EqpManipulation : IMetaManipulation< EqpManipulation >
|
public readonly struct EqpManipulation : IMetaManipulation< EqpManipulation >
|
||||||
{
|
{
|
||||||
[JsonConverter( typeof( ForceNumericFlagEnumConverter ) )]
|
[JsonConverter( typeof( ForceNumericFlagEnumConverter ) )]
|
||||||
public readonly EqpEntry Entry;
|
public EqpEntry Entry { get; init; }
|
||||||
|
|
||||||
public readonly ushort SetId;
|
public ushort SetId { get; init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter( typeof( StringEnumConverter ) )]
|
||||||
public readonly EquipSlot Slot;
|
public EquipSlot Slot { get; init; }
|
||||||
|
|
||||||
public EqpManipulation( EqpEntry entry, EquipSlot slot, ushort setId )
|
public EqpManipulation( EqpEntry entry, EquipSlot slot, ushort setId )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -19,18 +19,18 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
|
||||||
Head = CharacterUtility.HeadEstIdx,
|
Head = CharacterUtility.HeadEstIdx,
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly ushort Entry; // SkeletonIdx.
|
public ushort Entry { get; init; } // SkeletonIdx.
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter( typeof( StringEnumConverter ) )]
|
||||||
public readonly Gender Gender;
|
public Gender Gender { get; init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter( typeof( StringEnumConverter ) )]
|
||||||
public readonly ModelRace Race;
|
public ModelRace Race { get; init; }
|
||||||
|
|
||||||
public readonly ushort SetId;
|
public ushort SetId { get; init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter( typeof( StringEnumConverter ) )]
|
||||||
public readonly EstType Slot;
|
public EstType Slot { get; init; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public EstManipulation( Gender gender, ModelRace race, EstType slot, ushort setId, ushort entry )
|
public EstManipulation( Gender gender, ModelRace race, EstType slot, ushort setId, ushort entry )
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ 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 readonly GmpEntry Entry;
|
public GmpEntry Entry { get; init; }
|
||||||
public readonly ushort SetId;
|
public ushort SetId { get; init; }
|
||||||
|
|
||||||
public GmpManipulation( GmpEntry entry, ushort setId )
|
public GmpManipulation( GmpEntry entry, ushort setId )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,19 @@ namespace Penumbra.Meta.Manipulations;
|
||||||
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
|
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
|
||||||
public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation >
|
public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation >
|
||||||
{
|
{
|
||||||
public readonly ImcEntry Entry;
|
public ImcEntry Entry { get; init; }
|
||||||
public readonly ushort PrimaryId;
|
public ushort PrimaryId { get; init; }
|
||||||
public readonly ushort Variant;
|
public ushort Variant { get; init; }
|
||||||
public readonly ushort SecondaryId;
|
public ushort SecondaryId { get; init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter( typeof( StringEnumConverter ) )]
|
||||||
public readonly ObjectType ObjectType;
|
public ObjectType ObjectType { get; init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter( typeof( StringEnumConverter ) )]
|
||||||
public readonly EquipSlot EquipSlot;
|
public EquipSlot EquipSlot { get; init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter( typeof( StringEnumConverter ) )]
|
||||||
public readonly BodySlot BodySlot;
|
public BodySlot BodySlot { get; init; }
|
||||||
|
|
||||||
public ImcManipulation( EquipSlot equipSlot, ushort variant, ushort primaryId, ImcEntry entry )
|
public ImcManipulation( EquipSlot equipSlot, ushort variant, ushort primaryId, ImcEntry entry )
|
||||||
{
|
{
|
||||||
|
|
@ -52,19 +52,24 @@ public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation >
|
||||||
internal ImcManipulation( ObjectType objectType, BodySlot bodySlot, ushort primaryId, ushort secondaryId, ushort variant,
|
internal ImcManipulation( ObjectType objectType, BodySlot bodySlot, ushort primaryId, ushort secondaryId, ushort variant,
|
||||||
EquipSlot equipSlot, ImcEntry entry )
|
EquipSlot equipSlot, ImcEntry entry )
|
||||||
{
|
{
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
ObjectType = objectType;
|
ObjectType = objectType;
|
||||||
BodySlot = bodySlot;
|
PrimaryId = primaryId;
|
||||||
PrimaryId = primaryId;
|
Variant = variant;
|
||||||
SecondaryId = secondaryId;
|
if( objectType is ObjectType.Accessory or ObjectType.Equipment )
|
||||||
Variant = variant;
|
{
|
||||||
EquipSlot = equipSlot;
|
BodySlot = BodySlot.Unknown;
|
||||||
|
SecondaryId = 0;
|
||||||
|
EquipSlot = equipSlot;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BodySlot = bodySlot;
|
||||||
|
SecondaryId = secondaryId;
|
||||||
|
EquipSlot = EquipSlot.Unknown;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImcManipulation( ImcManipulation copy, ImcEntry entry )
|
|
||||||
: this( copy.ObjectType, copy.BodySlot, copy.PrimaryId, copy.SecondaryId, copy.Variant, copy.EquipSlot, entry )
|
|
||||||
{}
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
=> ObjectType is ObjectType.Equipment or ObjectType.Accessory
|
=> ObjectType is ObjectType.Equipment or ObjectType.Accessory
|
||||||
? $"Imc - {PrimaryId} - {EquipSlot} - {Variant}"
|
? $"Imc - {PrimaryId} - {EquipSlot} - {Variant}"
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,13 @@ 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 readonly float Entry;
|
public float Entry { get; init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter( typeof( StringEnumConverter ) )]
|
||||||
public readonly SubRace SubRace;
|
public SubRace SubRace { get; init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter( typeof( StringEnumConverter ) )]
|
||||||
public readonly RspAttribute Attribute;
|
public RspAttribute Attribute { get; init; }
|
||||||
|
|
||||||
public RspManipulation( SubRace subRace, RspAttribute attribute, float entry )
|
public RspManipulation( SubRace subRace, RspAttribute attribute, float entry )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.Meta.Manipulations;
|
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
@ -11,7 +10,7 @@ public partial class Mod
|
||||||
public partial class Editor
|
public partial class Editor
|
||||||
{
|
{
|
||||||
public int GroupIdx { get; private set; } = -1;
|
public int GroupIdx { get; private set; } = -1;
|
||||||
public int OptionIdx { get; private set; } = 0;
|
public int OptionIdx { get; private set; }
|
||||||
|
|
||||||
private IModGroup? _modGroup;
|
private IModGroup? _modGroup;
|
||||||
private SubMod _subMod;
|
private SubMod _subMod;
|
||||||
|
|
@ -21,7 +20,6 @@ public partial class Mod
|
||||||
|
|
||||||
public readonly Dictionary< Utf8GamePath, FullPath > CurrentFiles = new();
|
public readonly Dictionary< Utf8GamePath, FullPath > CurrentFiles = new();
|
||||||
public readonly Dictionary< Utf8GamePath, FullPath > CurrentSwaps = new();
|
public readonly Dictionary< Utf8GamePath, FullPath > CurrentSwaps = new();
|
||||||
public readonly HashSet< MetaManipulation > CurrentManipulations = new();
|
|
||||||
|
|
||||||
public void SetSubMod( int groupIdx, int optionIdx )
|
public void SetSubMod( int groupIdx, int optionIdx )
|
||||||
{
|
{
|
||||||
|
|
@ -62,16 +60,5 @@ public partial class Mod
|
||||||
{
|
{
|
||||||
CurrentSwaps.SetTo( _subMod.FileSwaps );
|
CurrentSwaps.SetTo( _subMod.FileSwaps );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyManipulations()
|
|
||||||
{
|
|
||||||
Penumbra.ModManager.OptionSetManipulations( _mod, GroupIdx, OptionIdx, CurrentManipulations.ToHashSet() );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RevertManipulations()
|
|
||||||
{
|
|
||||||
CurrentManipulations.Clear();
|
|
||||||
CurrentManipulations.UnionWith( _subMod.Manipulations );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
166
Penumbra/Mods/Editor/Mod.Editor.Meta.cs
Normal file
166
Penumbra/Mods/Editor/Mod.Editor.Meta.cs
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
public partial class Mod
|
||||||
|
{
|
||||||
|
public partial class Editor
|
||||||
|
{
|
||||||
|
public struct Manipulations
|
||||||
|
{
|
||||||
|
private readonly HashSet< ImcManipulation > _imc = new();
|
||||||
|
private readonly HashSet< EqpManipulation > _eqp = new();
|
||||||
|
private readonly HashSet< EqdpManipulation > _eqdp = new();
|
||||||
|
private readonly HashSet< GmpManipulation > _gmp = new();
|
||||||
|
private readonly HashSet< EstManipulation > _est = new();
|
||||||
|
private readonly HashSet< RspManipulation > _rsp = new();
|
||||||
|
|
||||||
|
public bool Changes { get; private set; } = false;
|
||||||
|
|
||||||
|
public IReadOnlySet< ImcManipulation > Imc
|
||||||
|
=> _imc;
|
||||||
|
|
||||||
|
public IReadOnlySet< EqpManipulation > Eqp
|
||||||
|
=> _eqp;
|
||||||
|
|
||||||
|
public IReadOnlySet< EqdpManipulation > Eqdp
|
||||||
|
=> _eqdp;
|
||||||
|
|
||||||
|
public IReadOnlySet< GmpManipulation > Gmp
|
||||||
|
=> _gmp;
|
||||||
|
|
||||||
|
public IReadOnlySet< EstManipulation > Est
|
||||||
|
=> _est;
|
||||||
|
|
||||||
|
public IReadOnlySet< RspManipulation > Rsp
|
||||||
|
=> _rsp;
|
||||||
|
|
||||||
|
public Manipulations()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public bool CanAdd( MetaManipulation m )
|
||||||
|
{
|
||||||
|
return m.ManipulationType switch
|
||||||
|
{
|
||||||
|
MetaManipulation.Type.Imc => !_imc.Contains( m.Imc ),
|
||||||
|
MetaManipulation.Type.Eqdp => !_eqdp.Contains( m.Eqdp ),
|
||||||
|
MetaManipulation.Type.Eqp => !_eqp.Contains( m.Eqp ),
|
||||||
|
MetaManipulation.Type.Est => !_est.Contains( m.Est ),
|
||||||
|
MetaManipulation.Type.Gmp => !_gmp.Contains( m.Gmp ),
|
||||||
|
MetaManipulation.Type.Rsp => !_rsp.Contains( m.Rsp ),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Add( MetaManipulation m )
|
||||||
|
{
|
||||||
|
var added = m.ManipulationType switch
|
||||||
|
{
|
||||||
|
MetaManipulation.Type.Imc => _imc.Add( m.Imc ),
|
||||||
|
MetaManipulation.Type.Eqdp => _eqdp.Add( m.Eqdp ),
|
||||||
|
MetaManipulation.Type.Eqp => _eqp.Add( m.Eqp ),
|
||||||
|
MetaManipulation.Type.Est => _est.Add( m.Est ),
|
||||||
|
MetaManipulation.Type.Gmp => _gmp.Add( m.Gmp ),
|
||||||
|
MetaManipulation.Type.Rsp => _rsp.Add( m.Rsp ),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
Changes |= added;
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Delete( MetaManipulation m )
|
||||||
|
{
|
||||||
|
var deleted = m.ManipulationType switch
|
||||||
|
{
|
||||||
|
MetaManipulation.Type.Imc => _imc.Remove( m.Imc ),
|
||||||
|
MetaManipulation.Type.Eqdp => _eqdp.Remove( m.Eqdp ),
|
||||||
|
MetaManipulation.Type.Eqp => _eqp.Remove( m.Eqp ),
|
||||||
|
MetaManipulation.Type.Est => _est.Remove( m.Est ),
|
||||||
|
MetaManipulation.Type.Gmp => _gmp.Remove( m.Gmp ),
|
||||||
|
MetaManipulation.Type.Rsp => _rsp.Remove( m.Rsp ),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
Changes |= deleted;
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Change( MetaManipulation m )
|
||||||
|
=> Delete( m ) && Add( m );
|
||||||
|
|
||||||
|
public bool Set( MetaManipulation m )
|
||||||
|
=> Delete( m ) | Add( m );
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_imc.Clear();
|
||||||
|
_eqp.Clear();
|
||||||
|
_eqdp.Clear();
|
||||||
|
_gmp.Clear();
|
||||||
|
_est.Clear();
|
||||||
|
_rsp.Clear();
|
||||||
|
Changes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Split( IEnumerable< MetaManipulation > manips )
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
foreach( var manip in manips )
|
||||||
|
{
|
||||||
|
switch( manip.ManipulationType )
|
||||||
|
{
|
||||||
|
case MetaManipulation.Type.Imc:
|
||||||
|
_imc.Add( manip.Imc );
|
||||||
|
break;
|
||||||
|
case MetaManipulation.Type.Eqdp:
|
||||||
|
_eqdp.Add( manip.Eqdp );
|
||||||
|
break;
|
||||||
|
case MetaManipulation.Type.Eqp:
|
||||||
|
_eqp.Add( manip.Eqp );
|
||||||
|
break;
|
||||||
|
case MetaManipulation.Type.Est:
|
||||||
|
_est.Add( manip.Est );
|
||||||
|
break;
|
||||||
|
case MetaManipulation.Type.Gmp:
|
||||||
|
_gmp.Add( manip.Gmp );
|
||||||
|
break;
|
||||||
|
case MetaManipulation.Type.Rsp:
|
||||||
|
_rsp.Add( manip.Rsp );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Changes = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashSet< MetaManipulation > Recombine()
|
||||||
|
=> _imc.Select( m => ( MetaManipulation )m )
|
||||||
|
.Concat( _eqdp.Select( m => ( MetaManipulation )m ) )
|
||||||
|
.Concat( _eqp.Select( m => ( MetaManipulation )m ) )
|
||||||
|
.Concat( _est.Select( m => ( MetaManipulation )m ) )
|
||||||
|
.Concat( _gmp.Select( m => ( MetaManipulation )m ) )
|
||||||
|
.Concat( _rsp.Select( m => ( MetaManipulation )m ) )
|
||||||
|
.ToHashSet();
|
||||||
|
|
||||||
|
public void Apply( Mod mod, int groupIdx, int optionIdx )
|
||||||
|
{
|
||||||
|
if( Changes )
|
||||||
|
{
|
||||||
|
Penumbra.ModManager.OptionSetManipulations( mod, groupIdx, optionIdx, Recombine() );
|
||||||
|
Changes = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Manipulations Meta = new();
|
||||||
|
|
||||||
|
public void RevertManipulations()
|
||||||
|
=> Meta.Split( _subMod.Manipulations );
|
||||||
|
|
||||||
|
public void ApplyManipulations()
|
||||||
|
{
|
||||||
|
Meta.Apply( _mod, GroupIdx, OptionIdx );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -278,7 +278,7 @@ public sealed partial class Mod
|
||||||
public void OptionSetManipulations( Mod mod, int groupIdx, int optionIdx, HashSet< MetaManipulation > manipulations )
|
public void OptionSetManipulations( Mod mod, int groupIdx, int optionIdx, HashSet< MetaManipulation > manipulations )
|
||||||
{
|
{
|
||||||
var subMod = GetSubMod( mod, groupIdx, optionIdx );
|
var subMod = GetSubMod( mod, groupIdx, optionIdx );
|
||||||
if( subMod.Manipulations.SetEquals( manipulations ) )
|
if( subMod.Manipulations.All( m => manipulations.TryGetValue( m, out var old ) && old.EntryEquals( m ) ) )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ public enum ColorId
|
||||||
FolderCollapsed,
|
FolderCollapsed,
|
||||||
FolderLine,
|
FolderLine,
|
||||||
ItemId,
|
ItemId,
|
||||||
|
IncreasedMetaValue,
|
||||||
|
DecreasedMetaValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Colors
|
public static class Colors
|
||||||
|
|
@ -30,18 +32,20 @@ public static class Colors
|
||||||
=> color switch
|
=> color switch
|
||||||
{
|
{
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
ColorId.EnabledMod => ( 0xFFFFFFFF, "Enabled Mod", "A mod that is enabled by the currently selected collection." ),
|
ColorId.EnabledMod => ( 0xFFFFFFFF, "Enabled Mod", "A mod that is enabled by the currently selected collection." ),
|
||||||
ColorId.DisabledMod => ( 0xFF686880, "Disabled Mod", "A mod that is disabled by the currently selected collection." ),
|
ColorId.DisabledMod => ( 0xFF686880, "Disabled Mod", "A mod that is disabled by the currently selected collection." ),
|
||||||
ColorId.UndefinedMod => ( 0xFF808080, "Mod With No Settings", "A mod that is not configured in the currently selected collection or any of the collections it inherits from, and thus implicitly disabled." ),
|
ColorId.UndefinedMod => ( 0xFF808080, "Mod With No Settings", "A mod that is not configured in the currently selected collection or any of the collections it inherits from, and thus implicitly disabled." ),
|
||||||
ColorId.InheritedMod => ( 0xFFD0FFFF, "Mod Enabled By Inheritance", "A mod that is not configured in the currently selected collection, but enabled in a collection it inherits from." ),
|
ColorId.InheritedMod => ( 0xFFD0FFFF, "Mod Enabled By Inheritance", "A mod that is not configured in the currently selected collection, but enabled in a collection it inherits from." ),
|
||||||
ColorId.InheritedDisabledMod => ( 0xFF688080, "Mod Disabled By Inheritance", "A mod that is not configured in the currently selected collection, but disabled in a collection it inherits from."),
|
ColorId.InheritedDisabledMod => ( 0xFF688080, "Mod Disabled By Inheritance", "A mod that is not configured in the currently selected collection, but disabled in a collection it inherits from."),
|
||||||
ColorId.NewMod => ( 0xFF66DD66, "New Mod", "A mod that was newly imported or created during this session and has not been enabled yet." ),
|
ColorId.NewMod => ( 0xFF66DD66, "New Mod", "A mod that was newly imported or created during this session and has not been enabled yet." ),
|
||||||
ColorId.ConflictingMod => ( 0xFFAAAAFF, "Mod With Unresolved Conflicts", "An enabled mod that has conflicts with another enabled mod on the same priority level." ),
|
ColorId.ConflictingMod => ( 0xFFAAAAFF, "Mod With Unresolved Conflicts", "An enabled mod that has conflicts with another enabled mod on the same priority level." ),
|
||||||
ColorId.HandledConflictMod => ( 0xFFD0FFD0, "Mod With Resolved Conflicts", "An enabled mod that has conflicts with another enabled mod on a different priority level." ),
|
ColorId.HandledConflictMod => ( 0xFFD0FFD0, "Mod With Resolved Conflicts", "An enabled mod that has conflicts with another enabled mod on a different priority level." ),
|
||||||
ColorId.FolderExpanded => ( 0xFFFFF0C0, "Expanded Mod Folder", "A mod folder that is currently expanded." ),
|
ColorId.FolderExpanded => ( 0xFFFFF0C0, "Expanded Mod Folder", "A mod folder that is currently expanded." ),
|
||||||
ColorId.FolderCollapsed => ( 0xFFFFF0C0, "Collapsed Mod Folder", "A mod folder that is currently collapsed." ),
|
ColorId.FolderCollapsed => ( 0xFFFFF0C0, "Collapsed Mod Folder", "A mod folder that is currently collapsed." ),
|
||||||
ColorId.FolderLine => ( 0xFFFFF0C0, "Expanded Mod Folder Line", "The line signifying which descendants belong to an expanded mod folder." ),
|
ColorId.FolderLine => ( 0xFFFFF0C0, "Expanded Mod Folder Line", "The line signifying which descendants belong to an expanded mod folder." ),
|
||||||
ColorId.ItemId => ( 0xFF808080, "Item Id", "The numeric model id of the given item to the right of changed items." ),
|
ColorId.ItemId => ( 0xFF808080, "Item Id", "The numeric model id of the given item to the right of changed items." ),
|
||||||
|
ColorId.IncreasedMetaValue => ( 0x80008000, "Increased Meta Manipulation Value", "An increased meta manipulation value for floats or an enabled toggle where the default is disabled."),
|
||||||
|
ColorId.DecreasedMetaValue => ( 0x80000080, "Decreased Meta Manipulation Value", "A decreased meta manipulation value for floats or a disabled toggle where the default is enabled."),
|
||||||
_ => throw new ArgumentOutOfRangeException( nameof( color ), color, null ),
|
_ => throw new ArgumentOutOfRangeException( nameof( color ), color, null ),
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
};
|
};
|
||||||
|
|
|
||||||
773
Penumbra/UI/Classes/ModEditWindow.Meta.cs
Normal file
773
Penumbra/UI/Classes/ModEditWindow.Meta.cs
Normal file
|
|
@ -0,0 +1,773 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.Meta.Files;
|
||||||
|
using Penumbra.Meta.Manipulations;
|
||||||
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
namespace Penumbra.UI.Classes;
|
||||||
|
|
||||||
|
public partial class ModEditWindow
|
||||||
|
{
|
||||||
|
private void DrawMetaTab()
|
||||||
|
{
|
||||||
|
using var tab = ImRaii.TabItem( "Meta Manipulations" );
|
||||||
|
if( !tab )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawOptionSelectHeader();
|
||||||
|
|
||||||
|
var setsEqual = !_editor!.Meta.Changes;
|
||||||
|
var tt = setsEqual ? "No changes staged." : "Apply the currently staged changes to the option.";
|
||||||
|
ImGui.NewLine();
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( "Apply Changes", Vector2.Zero, tt, setsEqual ) )
|
||||||
|
{
|
||||||
|
_editor.ApplyManipulations();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
tt = setsEqual ? "No changes staged." : "Revert all currently staged changes.";
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( "Revert Changes", Vector2.Zero, tt, setsEqual ) )
|
||||||
|
{
|
||||||
|
_editor.RevertManipulations();
|
||||||
|
}
|
||||||
|
|
||||||
|
using var child = ImRaii.Child( "##meta", -Vector2.One, true );
|
||||||
|
if( !child )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawEditHeader( _editor.Meta.Eqp, "Equipment Parameter Edits (EQP)###EQP", 4, EqpRow.Draw, EqpRow.DrawNew );
|
||||||
|
DrawEditHeader( _editor.Meta.Eqdp, "Racial Model Edits (EQDP)###EQDP", 6, EqdpRow.Draw, EqdpRow.DrawNew );
|
||||||
|
DrawEditHeader( _editor.Meta.Imc, "Variant Edits (IMC)###IMC", 8, ImcRow.Draw, ImcRow.DrawNew );
|
||||||
|
DrawEditHeader( _editor.Meta.Est, "Extra Skeleton Parameters (EST)###EST", 6, EstRow.Draw, EstRow.DrawNew );
|
||||||
|
DrawEditHeader( _editor.Meta.Gmp, "Visor/Gimmick Edits (GMP)###GMP", 6, GmpRow.Draw, GmpRow.DrawNew );
|
||||||
|
DrawEditHeader( _editor.Meta.Rsp, "Racial Scaling Edits (RSP)###RSP", 4, RspRow.Draw, RspRow.DrawNew );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The headers for the different meta changes all have basically the same structure for different types.
|
||||||
|
private void DrawEditHeader< T >( IReadOnlyCollection< T > items, string label, int numColumns, Action< T, Mod.Editor, Vector2 > draw,
|
||||||
|
Action< Mod.Editor, Vector2 > drawNew )
|
||||||
|
{
|
||||||
|
const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.BordersInnerV;
|
||||||
|
if( !ImGui.CollapsingHeader( $"{items.Count} {label}" ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using( var table = ImRaii.Table( label, numColumns, flags ) )
|
||||||
|
{
|
||||||
|
if( table )
|
||||||
|
{
|
||||||
|
drawNew( _editor!, _iconSize );
|
||||||
|
foreach( var (item, index) in items.ToArray().WithIndex() )
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId( index );
|
||||||
|
draw( item, _editor!, _iconSize );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class EqpRow
|
||||||
|
{
|
||||||
|
private static EqpManipulation _new = new(Eqp.DefaultEntry, EquipSlot.Head, 1);
|
||||||
|
|
||||||
|
private static float IdWidth
|
||||||
|
=> 100 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public static void DrawNew( Mod.Editor editor, Vector2 iconSize )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var canAdd = editor.Meta.CanAdd( _new );
|
||||||
|
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
|
||||||
|
var defaultEntry = ExpandedEqpFile.GetDefault( _new.SetId );
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Add( _new with { Entry = defaultEntry } );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( IdInput( "##eqpId", IdWidth, _new.SetId, out var setId, ExpandedEqpGmpBase.Count - 1 ) )
|
||||||
|
{
|
||||||
|
_new = _new with { SetId = setId };
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( EqpEquipSlotCombo( "##eqpSlot", _new.Slot, out var slot ) )
|
||||||
|
{
|
||||||
|
_new = _new with { Slot = slot };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2 );
|
||||||
|
foreach( var flag in Eqp.EqpAttributes[ _new.Slot ] )
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId( ( int )flag );
|
||||||
|
var value = defaultEntry.HasFlag( flag );
|
||||||
|
Checkmark( string.Empty, flag.ToLocalName(), value, value, out _ );
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Draw( EqpManipulation meta, Mod.Editor editor, Vector2 iconSize )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), iconSize, "Delete this meta edit.", false, true ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Delete( meta );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.SetId.ToString() );
|
||||||
|
var defaultEntry = ExpandedEqpFile.GetDefault( meta.SetId );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.Slot.ToName() );
|
||||||
|
|
||||||
|
// Values
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2 );
|
||||||
|
foreach( var flag in Eqp.EqpAttributes[ meta.Slot ] )
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId( ( int )flag );
|
||||||
|
var defaultValue = defaultEntry.HasFlag( flag );
|
||||||
|
var currentValue = meta.Entry.HasFlag( flag );
|
||||||
|
if( Checkmark( string.Empty, flag.ToLocalName(), currentValue, defaultValue, out var value ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = value ? meta.Entry | flag : meta.Entry & ~flag } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class EqdpRow
|
||||||
|
{
|
||||||
|
private static EqdpManipulation _new = new(EqdpEntry.Invalid, EquipSlot.Head, Gender.Male, ModelRace.Midlander, 1);
|
||||||
|
|
||||||
|
private static float IdWidth
|
||||||
|
=> 100 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public static void DrawNew( Mod.Editor editor, Vector2 iconSize )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var raceCode = Names.CombinedRace( _new.Gender, _new.Race );
|
||||||
|
var validRaceCode = CharacterUtility.EqdpIdx( raceCode, false ) >= 0;
|
||||||
|
var canAdd = validRaceCode && editor.Meta.CanAdd( _new );
|
||||||
|
var tt = canAdd ? "Stage this edit." :
|
||||||
|
validRaceCode ? "This entry is already edited." : "This combination of race and gender can not be used.";
|
||||||
|
var defaultEntry = validRaceCode
|
||||||
|
? ExpandedEqdpFile.GetDefault( Names.CombinedRace( _new.Gender, _new.Race ), _new.Slot.IsAccessory(), _new.SetId )
|
||||||
|
: 0;
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Add( _new with { Entry = defaultEntry } );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( IdInput( "##eqdpId", IdWidth, _new.SetId, out var setId, ExpandedEqpGmpBase.Count - 1 ) )
|
||||||
|
{
|
||||||
|
_new = _new with { SetId = setId };
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( RaceCombo( "##eqdpRace", _new.Race, out var race ) )
|
||||||
|
{
|
||||||
|
_new = _new with { Race = race };
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( GenderCombo( "##eqdpGender", _new.Gender, out var gender ) )
|
||||||
|
{
|
||||||
|
_new = _new with { Gender = gender };
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( EqdpEquipSlotCombo( "##eqdpSlot", _new.Slot, out var slot ) )
|
||||||
|
{
|
||||||
|
_new = _new with { Slot = slot };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var (bit1, bit2) = defaultEntry.ToBits( _new.Slot );
|
||||||
|
Checkmark( "##eqdpCheck1", string.Empty, bit1, bit1, out _ );
|
||||||
|
ImGui.SameLine();
|
||||||
|
Checkmark( "##eqdpCheck2", string.Empty, bit2, bit2, out _ );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Draw( EqdpManipulation meta, Mod.Editor editor, Vector2 iconSize )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), iconSize, "Delete this meta edit.", false, true ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Delete( meta );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.SetId.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.Race.ToName() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.Gender.ToName() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.Slot.ToName() );
|
||||||
|
|
||||||
|
// Values
|
||||||
|
var defaultEntry = ExpandedEqdpFile.GetDefault( Names.CombinedRace( meta.Gender, meta.Race ), meta.Slot.IsAccessory(), meta.SetId );
|
||||||
|
var (defaultBit1, defaultBit2) = defaultEntry.ToBits( meta.Slot );
|
||||||
|
var (bit1, bit2) = meta.Entry.ToBits( meta.Slot );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( Checkmark( "##eqdpCheck1", string.Empty, bit1, defaultBit1, out var newBit1 ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = Eqdp.FromSlotAndBits( meta.Slot, newBit1, bit2 ) } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if( Checkmark( "##eqdpCheck2", string.Empty, bit2, defaultBit2, out var newBit2 ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = Eqdp.FromSlotAndBits( meta.Slot, bit1, newBit2 ) } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ImcRow
|
||||||
|
{
|
||||||
|
private static ImcManipulation _new = new(EquipSlot.Head, 1, 1, new ImcEntry());
|
||||||
|
|
||||||
|
private static float IdWidth
|
||||||
|
=> 80 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
private static float SmallIdWidth
|
||||||
|
=> 45 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
// Convert throwing to null-return if the file does not exist.
|
||||||
|
private static ImcEntry? GetDefault( ImcManipulation imc )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ImcFile.GetDefault( imc.GamePath(), imc.EquipSlot, imc.Variant );
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawNew( Mod.Editor editor, Vector2 iconSize )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var defaultEntry = GetDefault( _new );
|
||||||
|
var canAdd = defaultEntry != null && editor.Meta.CanAdd( _new );
|
||||||
|
var tt = canAdd ? "Stage this edit." : defaultEntry == null ? "This IMC file does not exist." : "This entry is already edited.";
|
||||||
|
defaultEntry ??= new ImcEntry();
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Add( _new with { Entry = defaultEntry.Value } );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( ImcTypeCombo( "##imcType", _new.ObjectType, out var type ) )
|
||||||
|
{
|
||||||
|
_new = new ImcManipulation( type, _new.BodySlot, _new.PrimaryId, _new.SecondaryId == 0 ? ( ushort )1 : _new.SecondaryId,
|
||||||
|
_new.Variant, _new.EquipSlot == EquipSlot.Unknown ? EquipSlot.Head : _new.EquipSlot, _new.Entry );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( IdInput( "##imcId", IdWidth, _new.PrimaryId, out var setId, ushort.MaxValue ) )
|
||||||
|
{
|
||||||
|
_new = _new with { PrimaryId = setId };
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
// Equipment and accessories are slightly different imcs than other types.
|
||||||
|
if( _new.ObjectType is ObjectType.Equipment or ObjectType.Accessory )
|
||||||
|
{
|
||||||
|
if( EqdpEquipSlotCombo( "##imcSlot", _new.EquipSlot, out var slot ) )
|
||||||
|
{
|
||||||
|
_new = _new with { EquipSlot = slot };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( IdInput( "##imcId2", 100 * ImGuiHelpers.GlobalScale, _new.SecondaryId, out var setId2, ushort.MaxValue ) )
|
||||||
|
{
|
||||||
|
_new = _new with { SecondaryId = setId2 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( IdInput( "##imcVariant", SmallIdWidth, _new.Variant, out var variant, byte.MaxValue ) )
|
||||||
|
{
|
||||||
|
_new = _new with { Variant = variant };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
IntDragInput( "##imcMaterialId", "Material ID", SmallIdWidth, defaultEntry.Value.MaterialId, defaultEntry.Value.MaterialId, out _,
|
||||||
|
1, byte.MaxValue, 0f );
|
||||||
|
ImGui.SameLine();
|
||||||
|
IntDragInput( "##imcMaterialAnimId", "Material Animation ID", SmallIdWidth, defaultEntry.Value.MaterialAnimationId,
|
||||||
|
defaultEntry.Value.MaterialAnimationId, out _, 0, byte.MaxValue, 0.01f );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
IntDragInput( "##imcDecalId", "Decal ID", SmallIdWidth, defaultEntry.Value.DecalId, defaultEntry.Value.DecalId, out _, 0,
|
||||||
|
byte.MaxValue, 0f );
|
||||||
|
ImGui.SameLine();
|
||||||
|
IntDragInput( "##imcVfxId", "VFX ID", SmallIdWidth, defaultEntry.Value.VfxId, defaultEntry.Value.VfxId, out _, 0, byte.MaxValue,
|
||||||
|
0f );
|
||||||
|
ImGui.SameLine();
|
||||||
|
IntDragInput( "##imcSoundId", "Sound ID", SmallIdWidth, defaultEntry.Value.SoundId, defaultEntry.Value.SoundId, out _, 0, 0b111111,
|
||||||
|
0f );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
IntDragInput( "##imcAttributes", "Attributes", IdWidth, defaultEntry.Value.AttributeMask, defaultEntry.Value.AttributeMask, out _,
|
||||||
|
0, 0b1111111111, 0f );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Draw( ImcManipulation meta, Mod.Editor editor, Vector2 iconSize )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), iconSize, "Delete this meta edit.", false, true ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Delete( meta );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.ObjectType.ToName() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.PrimaryId.ToString() );
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
if( meta.ObjectType is ObjectType.Equipment or ObjectType.Accessory )
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted( meta.EquipSlot.ToName() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted( meta.SecondaryId.ToString() );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.Variant.ToString() );
|
||||||
|
|
||||||
|
// Values
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var defaultEntry = GetDefault( meta ) ?? new ImcEntry();
|
||||||
|
if( IntDragInput( "##imcMaterialId", $"Material ID\nDefault Value: {defaultEntry.MaterialId}", SmallIdWidth, meta.Entry.MaterialId,
|
||||||
|
defaultEntry.MaterialId, out var materialId, 1, byte.MaxValue, 0.01f ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = meta.Entry with { MaterialId = ( byte )materialId } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if( IntDragInput( "##imcMaterialAnimId", $"Material Animation ID\nDefault Value: {defaultEntry.MaterialAnimationId}", SmallIdWidth,
|
||||||
|
meta.Entry.MaterialAnimationId, defaultEntry.MaterialAnimationId, out var materialAnimId, 0, byte.MaxValue, 0.01f ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = meta.Entry with { MaterialAnimationId = ( byte )materialAnimId } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( IntDragInput( "##imcDecalId", $"Decal ID\nDefault Value: {defaultEntry.DecalId}", SmallIdWidth, meta.Entry.DecalId,
|
||||||
|
defaultEntry.DecalId, out var decalId, 0, byte.MaxValue, 0.01f ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = meta.Entry with { DecalId = ( byte )decalId } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if( IntDragInput( "##imcVfxId", $"VFX ID\nDefault Value: {defaultEntry.VfxId}", SmallIdWidth, meta.Entry.VfxId, defaultEntry.VfxId,
|
||||||
|
out var vfxId, 0, byte.MaxValue, 0.01f ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = meta.Entry with { VfxId = ( byte )vfxId } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if( IntDragInput( "##imcSoundId", $"Sound ID\nDefault Value: {defaultEntry.SoundId}", SmallIdWidth, meta.Entry.SoundId,
|
||||||
|
defaultEntry.SoundId, out var soundId, 0, 0b111111, 0.01f ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = meta.Entry with { SoundId = ( byte )soundId } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( IntDragInput( "##imcAttributes", $"Attributes\nDefault Value: {defaultEntry.AttributeMask}", IdWidth,
|
||||||
|
meta.Entry.AttributeMask, defaultEntry.AttributeMask, out var attributeMask, 0, 0b1111111111, 0.1f ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = meta.Entry with { AttributeMask = ( ushort )attributeMask } } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class EstRow
|
||||||
|
{
|
||||||
|
private static EstManipulation _new = new(Gender.Male, ModelRace.Midlander, EstManipulation.EstType.Body, 1, 0);
|
||||||
|
|
||||||
|
private static float IdWidth
|
||||||
|
=> 100 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public static void DrawNew( Mod.Editor editor, Vector2 iconSize )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var canAdd = editor.Meta.CanAdd( _new );
|
||||||
|
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
|
||||||
|
var defaultEntry = EstFile.GetDefault( _new.Slot, Names.CombinedRace( _new.Gender, _new.Race ), _new.SetId );
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Add( _new with { Entry = defaultEntry } );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( IdInput( "##estId", IdWidth, _new.SetId, out var setId, ExpandedEqpGmpBase.Count - 1 ) )
|
||||||
|
{
|
||||||
|
_new = _new with { SetId = setId };
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( RaceCombo( "##estRace", _new.Race, out var race ) )
|
||||||
|
{
|
||||||
|
_new = _new with { Race = race };
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( GenderCombo( "##estGender", _new.Gender, out var gender ) )
|
||||||
|
{
|
||||||
|
_new = _new with { Gender = gender };
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( EstSlotCombo( "##estSlot", _new.Slot, out var slot ) )
|
||||||
|
{
|
||||||
|
_new = _new with { Slot = slot };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
IntDragInput( "##estSkeleton", "Skeleton Index", IdWidth, _new.Entry, defaultEntry, out _, 0, ushort.MaxValue, 0.05f );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Draw( EstManipulation meta, Mod.Editor editor, Vector2 iconSize )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), iconSize, "Delete this meta edit.", false, true ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Delete( meta );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.SetId.ToString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.Race.ToName() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.Gender.ToName() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.Slot.ToString() );
|
||||||
|
|
||||||
|
// Values
|
||||||
|
var defaultEntry = EstFile.GetDefault( meta.Slot, Names.CombinedRace( meta.Gender, meta.Race ), meta.SetId );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( IntDragInput( "##estSkeleton", $"Skeleton Index\nDefault Value: {defaultEntry}", IdWidth, meta.Entry, defaultEntry,
|
||||||
|
out var entry, 0, ushort.MaxValue, 0.05f ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = ( ushort )entry } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GmpRow
|
||||||
|
{
|
||||||
|
private static GmpManipulation _new = new(GmpEntry.Default, 1);
|
||||||
|
|
||||||
|
private static float RotationWidth
|
||||||
|
=> 75 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
private static float UnkWidth
|
||||||
|
=> 50 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
private static float IdWidth
|
||||||
|
=> 100 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public static void DrawNew( Mod.Editor editor, Vector2 iconSize )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var canAdd = editor.Meta.CanAdd( _new );
|
||||||
|
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
|
||||||
|
var defaultEntry = ExpandedGmpFile.GetDefault( _new.SetId );
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Add( _new with { Entry = defaultEntry } );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( IdInput( "##gmpId", IdWidth, _new.SetId, out var setId, ExpandedEqpGmpBase.Count - 1 ) )
|
||||||
|
{
|
||||||
|
_new = _new with { SetId = setId };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
Checkmark( "##gmpEnabled", "Gimmick Enabled", defaultEntry.Enabled, defaultEntry.Enabled, out _ );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
Checkmark( "##gmpAnimated", "Gimmick Animated", defaultEntry.Animated, defaultEntry.Animated, out _ );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
IntDragInput( "##gmpRotationA", "Rotation A in Degrees", RotationWidth, defaultEntry.RotationA, defaultEntry.RotationA, out _, 0,
|
||||||
|
360, 0f );
|
||||||
|
ImGui.SameLine();
|
||||||
|
IntDragInput( "##gmpRotationB", "Rotation B in Degrees", RotationWidth, defaultEntry.RotationB, defaultEntry.RotationB, out _, 0,
|
||||||
|
360, 0f );
|
||||||
|
ImGui.SameLine();
|
||||||
|
IntDragInput( "##gmpRotationC", "Rotation C in Degrees", RotationWidth, defaultEntry.RotationC, defaultEntry.RotationC, out _, 0,
|
||||||
|
360, 0f );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
IntDragInput( "##gmpUnkA", "Animation Type A?", UnkWidth, defaultEntry.UnknownA, defaultEntry.UnknownA, out _, 0, 15, 0f );
|
||||||
|
ImGui.SameLine();
|
||||||
|
IntDragInput( "##gmpUnkB", "Animation Type B?", UnkWidth, defaultEntry.UnknownB, defaultEntry.UnknownB, out _, 0, 15, 0f );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Draw( GmpManipulation meta, Mod.Editor editor, Vector2 iconSize )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), iconSize, "Delete this meta edit.", false, true ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Delete( meta );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.SetId.ToString() );
|
||||||
|
|
||||||
|
// Values
|
||||||
|
var defaultEntry = ExpandedGmpFile.GetDefault( meta.SetId );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( Checkmark( "##gmpEnabled", "Gimmick Enabled", meta.Entry.Enabled, defaultEntry.Enabled, out var enabled ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = meta.Entry with { Enabled = enabled } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( Checkmark( "##gmpAnimated", "Gimmick Animated", meta.Entry.Animated, defaultEntry.Animated, out var animated ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = meta.Entry with { Animated = animated } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( IntDragInput( "##gmpRotationA", $"Rotation A in Degrees\nDefault Value: {defaultEntry.RotationA}", RotationWidth,
|
||||||
|
meta.Entry.RotationA, defaultEntry.RotationA, out var rotationA, 0, 360, 0.05f ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = meta.Entry with { RotationA = ( ushort )rotationA } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if( IntDragInput( "##gmpRotationB", $"Rotation B in Degrees\nDefault Value: {defaultEntry.RotationB}", RotationWidth,
|
||||||
|
meta.Entry.RotationB, defaultEntry.RotationB, out var rotationB, 0, 360, 0.05f ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = meta.Entry with { RotationB = ( ushort )rotationB } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if( IntDragInput( "##gmpRotationC", $"Rotation C in Degrees\nDefault Value: {defaultEntry.RotationC}", RotationWidth,
|
||||||
|
meta.Entry.RotationC, defaultEntry.RotationC, out var rotationC, 0, 360, 0.05f ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = meta.Entry with { RotationC = ( ushort )rotationC } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( IntDragInput( "##gmpUnkA", $"Animation Type A?\nDefault Value: {defaultEntry.UnknownA}", UnkWidth, meta.Entry.UnknownA,
|
||||||
|
defaultEntry.UnknownA, out var unkA, 0, 15, 0.01f ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = meta.Entry with { UnknownA = ( byte )unkA } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if( IntDragInput( "##gmpUnkB", $"Animation Type B?\nDefault Value: {defaultEntry.UnknownB}", UnkWidth, meta.Entry.UnknownB,
|
||||||
|
defaultEntry.UnknownB, out var unkB, 0, 15, 0.01f ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = meta.Entry with { UnknownA = ( byte )unkB } } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RspRow
|
||||||
|
{
|
||||||
|
private static RspManipulation _new = new(SubRace.Midlander, RspAttribute.MaleMinSize, 1f);
|
||||||
|
|
||||||
|
private static float FloatWidth
|
||||||
|
=> 150 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public static void DrawNew( Mod.Editor editor, Vector2 iconSize )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
var canAdd = editor.Meta.CanAdd( _new );
|
||||||
|
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
|
||||||
|
var defaultEntry = CmpFile.GetDefault( _new.SubRace, _new.Attribute );
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Add( _new with { Entry = defaultEntry } );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( SubRaceCombo( "##rspSubRace", _new.SubRace, out var subRace ) )
|
||||||
|
{
|
||||||
|
_new = _new with { SubRace = subRace };
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( RspAttributeCombo( "##rspAttribute", _new.Attribute, out var attribute ) )
|
||||||
|
{
|
||||||
|
_new = _new with { Attribute = attribute };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetNextItemWidth( FloatWidth );
|
||||||
|
ImGui.DragFloat( "##rspValue", ref defaultEntry, 0f );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Draw( RspManipulation meta, Mod.Editor editor, Vector2 iconSize )
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), iconSize, "Delete this meta edit.", false, true ) )
|
||||||
|
{
|
||||||
|
editor.Meta.Delete( meta );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.SubRace.ToName() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X );
|
||||||
|
ImGui.TextUnformatted( meta.Attribute.ToFullString() );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
// Values
|
||||||
|
var def = CmpFile.GetDefault( meta.SubRace, meta.Attribute );
|
||||||
|
var value = meta.Entry;
|
||||||
|
ImGui.SetNextItemWidth( FloatWidth );
|
||||||
|
using var color = ImRaii.PushColor( ImGuiCol.FrameBg,
|
||||||
|
def < value ? ColorId.IncreasedMetaValue.Value() : ColorId.DecreasedMetaValue.Value(),
|
||||||
|
def != value );
|
||||||
|
if( ImGui.DragFloat( "##rspValue", ref value, 0.001f, 0.01f, 8f ) && value is >= 0.01f and <= 8f )
|
||||||
|
{
|
||||||
|
editor.Meta.Change( meta with { Entry = value } );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip( $"Default Value: {def:0.###}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different combos to use with enums.
|
||||||
|
private static bool RaceCombo( string label, ModelRace current, out ModelRace race )
|
||||||
|
=> ImGuiUtil.GenericEnumCombo( label, 100 * ImGuiHelpers.GlobalScale, current, out race, RaceEnumExtensions.ToName, 1 );
|
||||||
|
|
||||||
|
private static bool GenderCombo( string label, Gender current, out Gender gender )
|
||||||
|
=> ImGuiUtil.GenericEnumCombo( label, 120 * ImGuiHelpers.GlobalScale, current, out gender, RaceEnumExtensions.ToName, 1 );
|
||||||
|
|
||||||
|
private static bool EqdpEquipSlotCombo( string label, EquipSlot current, out EquipSlot slot )
|
||||||
|
=> ImGuiUtil.GenericEnumCombo( label, 100 * ImGuiHelpers.GlobalScale, current, out slot, EquipSlotExtensions.EqdpSlots,
|
||||||
|
EquipSlotExtensions.ToName );
|
||||||
|
|
||||||
|
private static bool EqpEquipSlotCombo( string label, EquipSlot current, out EquipSlot slot )
|
||||||
|
=> ImGuiUtil.GenericEnumCombo( label, 100 * ImGuiHelpers.GlobalScale, current, out slot, EquipSlotExtensions.EquipmentSlots,
|
||||||
|
EquipSlotExtensions.ToName );
|
||||||
|
|
||||||
|
private static bool SubRaceCombo( string label, SubRace current, out SubRace subRace )
|
||||||
|
=> ImGuiUtil.GenericEnumCombo( label, 150 * ImGuiHelpers.GlobalScale, current, out subRace, RaceEnumExtensions.ToName, 1 );
|
||||||
|
|
||||||
|
private static bool RspAttributeCombo( string label, RspAttribute current, out RspAttribute attribute )
|
||||||
|
=> ImGuiUtil.GenericEnumCombo( label, 200 * ImGuiHelpers.GlobalScale, current, out attribute,
|
||||||
|
RspAttributeExtensions.ToFullString, 0, 1 );
|
||||||
|
|
||||||
|
private static bool EstSlotCombo( string label, EstManipulation.EstType current, out EstManipulation.EstType attribute )
|
||||||
|
=> ImGuiUtil.GenericEnumCombo( label, 200 * ImGuiHelpers.GlobalScale, current, out attribute );
|
||||||
|
|
||||||
|
private static bool ImcTypeCombo( string label, ObjectType current, out ObjectType type )
|
||||||
|
=> ImGuiUtil.GenericEnumCombo( label, 110 * ImGuiHelpers.GlobalScale, current, out type, ObjectTypeExtensions.ValidImcTypes,
|
||||||
|
ObjectTypeExtensions.ToName );
|
||||||
|
|
||||||
|
// A number input for ids with a optional max id of given width.
|
||||||
|
// Returns true if newId changed against currentId.
|
||||||
|
private static bool IdInput( string label, float width, ushort currentId, out ushort newId, int maxId )
|
||||||
|
{
|
||||||
|
int tmp = currentId;
|
||||||
|
ImGui.SetNextItemWidth( width );
|
||||||
|
if( ImGui.InputInt( label, ref tmp, 0 ) )
|
||||||
|
{
|
||||||
|
tmp = Math.Clamp( tmp, 1, maxId );
|
||||||
|
}
|
||||||
|
|
||||||
|
newId = ( ushort )tmp;
|
||||||
|
return newId != currentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A checkmark that compares against a default value and shows a tooltip.
|
||||||
|
// Returns true if newValue is changed against currentValue.
|
||||||
|
private static bool Checkmark( string label, string tooltip, bool currentValue, bool defaultValue, out bool newValue )
|
||||||
|
{
|
||||||
|
using var color = ImRaii.PushColor( ImGuiCol.FrameBg,
|
||||||
|
defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(), defaultValue != currentValue );
|
||||||
|
newValue = currentValue;
|
||||||
|
ImGui.Checkbox( label, ref newValue );
|
||||||
|
ImGuiUtil.HoverTooltip( tooltip );
|
||||||
|
return newValue != currentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A dragging int input of given width that compares against a default value, shows a tooltip and clamps against min and max.
|
||||||
|
// Returns true if newValue changed against currentValue.
|
||||||
|
private static bool IntDragInput( string label, string tooltip, float width, int currentValue, int defaultValue, out int newValue,
|
||||||
|
int minValue, int maxValue, float speed )
|
||||||
|
{
|
||||||
|
newValue = currentValue;
|
||||||
|
using var color = ImRaii.PushColor( ImGuiCol.FrameBg,
|
||||||
|
defaultValue > currentValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(),
|
||||||
|
defaultValue != currentValue );
|
||||||
|
ImGui.SetNextItemWidth( width );
|
||||||
|
if( ImGui.DragInt( label, ref newValue, speed, minValue, maxValue ) )
|
||||||
|
{
|
||||||
|
newValue = Math.Clamp( newValue, minValue, maxValue );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip( tooltip );
|
||||||
|
|
||||||
|
return newValue != currentValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
|
@ -10,17 +9,18 @@ using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.UI.Classes;
|
namespace Penumbra.UI.Classes;
|
||||||
|
|
||||||
public class ModEditWindow : Window, IDisposable
|
public partial class ModEditWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
private const string WindowBaseLabel = "###SubModEdit";
|
private const string WindowBaseLabel = "###SubModEdit";
|
||||||
private Mod.Editor? _editor;
|
private Mod.Editor? _editor;
|
||||||
private Mod? _mod;
|
private Mod? _mod;
|
||||||
|
private Vector2 _iconSize = Vector2.Zero;
|
||||||
|
|
||||||
public void ChangeMod( Mod mod )
|
public void ChangeMod( Mod mod )
|
||||||
{
|
{
|
||||||
|
|
@ -54,6 +54,7 @@ public class ModEditWindow : Window, IDisposable
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_iconSize = new Vector2( ImGui.GetFrameHeight() );
|
||||||
DrawFileTab();
|
DrawFileTab();
|
||||||
DrawMetaTab();
|
DrawMetaTab();
|
||||||
DrawSwapTab();
|
DrawSwapTab();
|
||||||
|
|
@ -117,8 +118,9 @@ public class ModEditWindow : Window, IDisposable
|
||||||
: disabled
|
: disabled
|
||||||
? "The suffix is invalid."
|
? "The suffix is invalid."
|
||||||
: _materialSuffixFrom.Length == 0
|
: _materialSuffixFrom.Length == 0
|
||||||
? _raceCode == GenderRace.Unknown ? "Convert all skin material suffices to the target."
|
? _raceCode == GenderRace.Unknown
|
||||||
: "Convert all skin material suffices for the given race code to the target."
|
? "Convert all skin material suffices to the target."
|
||||||
|
: "Convert all skin material suffices for the given race code to the target."
|
||||||
: _raceCode == GenderRace.Unknown
|
: _raceCode == GenderRace.Unknown
|
||||||
? $"Convert all skin material suffices that are currently '{_materialSuffixFrom}' to '{_materialSuffixTo}'."
|
? $"Convert all skin material suffices that are currently '{_materialSuffixFrom}' to '{_materialSuffixTo}'."
|
||||||
: $"Convert all skin material suffices for the given race code that are currently '{_materialSuffixFrom}' to '{_materialSuffixTo}'.";
|
: $"Convert all skin material suffices for the given race code that are currently '{_materialSuffixFrom}' to '{_materialSuffixTo}'.";
|
||||||
|
|
@ -374,6 +376,7 @@ public class ModEditWindow : Window, IDisposable
|
||||||
isDefaultOption ) )
|
isDefaultOption ) )
|
||||||
{
|
{
|
||||||
_editor.SetSubMod( -1, 0 );
|
_editor.SetSubMod( -1, 0 );
|
||||||
|
isDefaultOption = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
@ -395,8 +398,7 @@ public class ModEditWindow : Window, IDisposable
|
||||||
return $"{group.Name}: {group[ _editor.OptionIdx ].Name}";
|
return $"{group.Name}: {group[ _editor.OptionIdx ].Name}";
|
||||||
}
|
}
|
||||||
|
|
||||||
var groupLabel = GetLabel();
|
using var combo = ImRaii.Combo( "##optionSelector", GetLabel(), ImGuiComboFlags.NoArrowButton );
|
||||||
using var combo = ImRaii.Combo( "##optionSelector", groupLabel, ImGuiComboFlags.NoArrowButton );
|
|
||||||
if( !combo )
|
if( !combo )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -497,74 +499,9 @@ public class ModEditWindow : Window, IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawMetaTab()
|
|
||||||
{
|
|
||||||
using var tab = ImRaii.TabItem( "Meta Manipulations" );
|
|
||||||
if( !tab )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawOptionSelectHeader();
|
|
||||||
|
|
||||||
var setsEqual = _editor!.CurrentManipulations.SetEquals( _editor.CurrentOption.Manipulations );
|
|
||||||
var tt = setsEqual ? "No changes staged." : "Apply the currently staged changes to the option.";
|
|
||||||
ImGui.NewLine();
|
|
||||||
if( ImGuiUtil.DrawDisabledButton( "Apply Changes", Vector2.Zero, tt, setsEqual ) )
|
|
||||||
{
|
|
||||||
_editor.ApplyManipulations();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
tt = setsEqual ? "No changes staged." : "Revert all currently staged changes.";
|
|
||||||
if( ImGuiUtil.DrawDisabledButton( "Revert Changes", Vector2.Zero, tt, setsEqual ) )
|
|
||||||
{
|
|
||||||
_editor.RevertManipulations();
|
|
||||||
}
|
|
||||||
|
|
||||||
using var child = ImRaii.Child( "##meta", -Vector2.One, true );
|
|
||||||
if( !child )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var list = ImRaii.Table( "##table", 3 );
|
|
||||||
if( !list )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach( var manip in _editor!.CurrentManipulations )
|
|
||||||
{
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.TextUnformatted( manip.ManipulationType.ToString() );
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.TextUnformatted( manip.ManipulationType switch
|
|
||||||
{
|
|
||||||
MetaManipulation.Type.Imc => manip.Imc.ToString(),
|
|
||||||
MetaManipulation.Type.Eqdp => manip.Eqdp.ToString(),
|
|
||||||
MetaManipulation.Type.Eqp => manip.Eqp.ToString(),
|
|
||||||
MetaManipulation.Type.Est => manip.Est.ToString(),
|
|
||||||
MetaManipulation.Type.Gmp => manip.Gmp.ToString(),
|
|
||||||
MetaManipulation.Type.Rsp => manip.Rsp.ToString(),
|
|
||||||
_ => string.Empty,
|
|
||||||
} );
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.TextUnformatted( manip.ManipulationType switch
|
|
||||||
{
|
|
||||||
MetaManipulation.Type.Imc => manip.Imc.Entry.ToString(),
|
|
||||||
MetaManipulation.Type.Eqdp => manip.Eqdp.Entry.ToString(),
|
|
||||||
MetaManipulation.Type.Eqp => manip.Eqp.Entry.ToString(),
|
|
||||||
MetaManipulation.Type.Est => manip.Est.Entry.ToString(),
|
|
||||||
MetaManipulation.Type.Gmp => manip.Gmp.Entry.ToString(),
|
|
||||||
MetaManipulation.Type.Rsp => manip.Rsp.Entry.ToString(),
|
|
||||||
_ => string.Empty,
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string _newSwapKey = string.Empty;
|
private string _newSwapKey = string.Empty;
|
||||||
private string _newSwapValue = string.Empty;
|
private string _newSwapValue = string.Empty;
|
||||||
|
|
||||||
private void DrawSwapTab()
|
private void DrawSwapTab()
|
||||||
{
|
{
|
||||||
using var tab = ImRaii.TabItem( "File Swaps" );
|
using var tab = ImRaii.TabItem( "File Swaps" );
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
@ -96,6 +97,11 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
PluginLog.Error( $"Could not create directory for new Mod {_newModName}:\n{e}" );
|
PluginLog.Error( $"Could not create directory for new Mod {_newModName}:\n{e}" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while( _modsToAdd.TryDequeue( out var dir ) )
|
||||||
|
{
|
||||||
|
Penumbra.ModManager.AddMod( dir );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DrawLeafName( FileSystem< Mod >.Leaf leaf, in ModState state, bool selected )
|
protected override void DrawLeafName( FileSystem< Mod >.Leaf leaf, in ModState state, bool selected )
|
||||||
|
|
@ -212,9 +218,12 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mods need to be added thread-safely outside of iteration.
|
||||||
|
private readonly ConcurrentQueue< DirectoryInfo > _modsToAdd = new();
|
||||||
|
|
||||||
// Clean up invalid directory if necessary.
|
// Clean up invalid directory if necessary.
|
||||||
// Add successfully extracted mods.
|
// Add successfully extracted mods.
|
||||||
private static void AddNewMod( FileInfo file, DirectoryInfo? dir, Exception? error )
|
private void AddNewMod( FileInfo file, DirectoryInfo? dir, Exception? error )
|
||||||
{
|
{
|
||||||
if( error != null )
|
if( error != null )
|
||||||
{
|
{
|
||||||
|
|
@ -237,7 +246,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
}
|
}
|
||||||
else if( dir != null )
|
else if( dir != null )
|
||||||
{
|
{
|
||||||
Penumbra.ModManager.AddMod( dir );
|
_modsToAdd.Enqueue( dir );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
|
||||||
|
|
||||||
namespace Penumbra.Util;
|
namespace Penumbra.Util;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue