diff --git a/PCK-Studio/Controls/Editor/BoxEditorControl.cs b/PCK-Studio/Controls/Editor/BoxEditorControl.cs
index 5682b146..034c1f58 100644
--- a/PCK-Studio/Controls/Editor/BoxEditorControl.cs
+++ b/PCK-Studio/Controls/Editor/BoxEditorControl.cs
@@ -83,10 +83,10 @@ namespace PckStudio.Controls
SizeZUpDown.Value = (decimal)box.Size.Z;
uvXUpDown.Value = (decimal)box.UV.X;
uvYUpDown.Value = (decimal)box.UV.Y;
- helmetCheckBox.Checked = (box.ArmorMaskFlags & 1) != 0;
- chestplateCheckBox.Checked = (box.ArmorMaskFlags & 2) != 0;
- leggingsCheckBox.Checked = (box.ArmorMaskFlags & 4) != 0;
- bootsCheckBox.Checked = (box.ArmorMaskFlags & 8) != 0;
+ helmetCheckBox.Checked = box.ArmorMaskFlags.GetFlag(SkinArmorFlagsFlag.HELMET);
+ chestplateCheckBox.Checked = box.ArmorMaskFlags.GetFlag(SkinArmorFlagsFlag.CHESTPLATE);
+ leggingsCheckBox.Checked = box.ArmorMaskFlags.GetFlag(SkinArmorFlagsFlag.LEGGINGS);
+ bootsCheckBox.Checked = box.ArmorMaskFlags.GetFlag(SkinArmorFlagsFlag.BOOTS);
mirrorCheckBox.Checked = box.Mirror;
// if the XMLVersion doesn't support scaling, set this value to 0 because it's not supported and fixes a rendering issue - May
inflationUpDown.Value = boxVersion == 3 ? (decimal)box.Scale : 0;
diff --git a/PckStudio.Core/PckStudio.Core.csproj b/PckStudio.Core/PckStudio.Core.csproj
index 3ee2df97..d0ccec87 100644
--- a/PckStudio.Core/PckStudio.Core.csproj
+++ b/PckStudio.Core/PckStudio.Core.csproj
@@ -131,7 +131,10 @@
+
+
+
diff --git a/PckStudio.Core/Skin/SkinArmorFlags.cs b/PckStudio.Core/Skin/SkinArmorFlags.cs
new file mode 100644
index 00000000..fffade3b
--- /dev/null
+++ b/PckStudio.Core/Skin/SkinArmorFlags.cs
@@ -0,0 +1,120 @@
+/* Copyright (c) 2026-present miku-666, MayNL
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+**/
+using System;
+using System.Collections.Specialized;
+using System.Text.RegularExpressions;
+
+namespace PckStudio.Core.Skin
+{
+ ///
+ /// Represents a SkinBOX ArmorFlags value where flags can be set
+ ///
+ public class SkinArmorFlags : ICloneable, IEquatable, IEquatable
+ {
+ public static readonly SkinArmorFlags Empty = new SkinArmorFlags(0);
+ public static readonly SkinArmorFlags HeadArmor = new SkinArmorFlags(1);
+ public static readonly SkinArmorFlags ChestplateAndArmArmor = new SkinArmorFlags(2);
+ public static readonly SkinArmorFlags Leggings = new SkinArmorFlags(4);
+ public static readonly SkinArmorFlags Belt = new SkinArmorFlags(6);
+ public static readonly SkinArmorFlags Boots = new SkinArmorFlags(8);
+ public static readonly SkinArmorFlags Socks = new SkinArmorFlags(12);
+
+ private BitVector32 _flags;
+ private static readonly Regex _validator = new Regex(@"^(0|[1-9]|1[0-6])$", RegexOptions.CultureInvariant);
+
+ public SkinArmorFlags(SkinArmorFlagsMask mask)
+ : this((int)mask)
+ {
+ }
+
+ public SkinArmorFlags(int mask)
+ {
+ _flags = new BitVector32(mask);
+ }
+
+ public override string ToString() => _flags.Data.ToString(); // decimal instead of hexadecimal like the other bitfields in the game
+
+ public static bool IsValidArmorFlags(string gameFlags)
+ {
+ return !string.IsNullOrWhiteSpace(gameFlags) && _validator.IsMatch(gameFlags);
+ }
+
+ public static SkinArmorFlags FromString(string value)
+ => IsValidArmorFlags(value)
+ ? new SkinArmorFlags(Convert.ToInt32(value))
+ : Empty;
+
+ public static SkinArmorFlags operator |(SkinArmorFlags @this, SkinArmorFlags other) => new SkinArmorFlags(@this._flags.Data | other._flags.Data);
+
+ public static SkinArmorFlags operator |(SkinArmorFlags @this, SkinArmorFlagsMask mask) => new SkinArmorFlags(@this._flags.Data | (int)mask);
+ public static SkinArmorFlags operator &(SkinArmorFlags @this, SkinArmorFlagsMask mask) => new SkinArmorFlags(@this._flags.Data & (int)mask);
+ public static SkinArmorFlags FromValue(int value) => new SkinArmorFlags(value);
+
+ public int ToValue() => _flags.Data;
+
+ public static implicit operator SkinArmorFlags(SkinArmorFlagsMask mask) => new SkinArmorFlags(mask);
+
+ public static bool operator ==(SkinArmorFlags @this, SkinArmorFlagsMask mask) => @this.Equals(mask);
+ public static bool operator !=(SkinArmorFlags @this, SkinArmorFlagsMask mask) => !@this.Equals(mask);
+ public static bool operator ==(SkinArmorFlags @this, SkinArmorFlags other) => @this.Equals(other);
+ public static bool operator !=(SkinArmorFlags @this, SkinArmorFlags other) => !@this.Equals(other);
+
+ public bool Equals(SkinArmorFlags other)
+ {
+ return _flags.Data == other._flags.Data;
+ }
+
+ public bool Equals(SkinArmorFlagsMask other)
+ {
+ return _flags.Data == (int)other;
+ }
+
+ public override bool Equals(object obj) => obj is SkinArmorFlags a && Equals(a);
+
+ public override int GetHashCode() => _flags.Data;
+
+ ///
+ /// Sets the desired flag in the bitfield
+ ///
+ /// SkinArmor Flag to set
+ /// State of the flag
+ public SkinArmorFlags SetFlag(SkinArmorFlagsFlag flag, bool state)
+ {
+ if (!Enum.IsDefined(typeof(SkinArmorFlagsFlag), flag))
+ throw new ArgumentOutOfRangeException(nameof(flag));
+ return new SkinArmorFlags(state ? _flags.Data | 1 << (int)flag : _flags.Data & ~(1 << (int)flag));
+ }
+
+ ///
+ /// Gets flag state
+ ///
+ /// Flag to check
+ /// True if flag is set, otherwise false
+ public bool GetFlag(SkinArmorFlagsFlag flag)
+ {
+ if (!Enum.IsDefined(typeof(SkinArmorFlagsFlag), flag))
+ throw new ArgumentOutOfRangeException(nameof(flag));
+ return _flags[1 << (int)flag];
+ }
+
+ public object Clone()
+ {
+ return MemberwiseClone();
+ }
+ }
+}
diff --git a/PckStudio.Core/Skin/SkinArmorFlagsFlag.cs b/PckStudio.Core/Skin/SkinArmorFlagsFlag.cs
new file mode 100644
index 00000000..a0d79633
--- /dev/null
+++ b/PckStudio.Core/Skin/SkinArmorFlagsFlag.cs
@@ -0,0 +1,13 @@
+namespace PckStudio.Core.Skin
+{
+ ///
+ /// For usage see
+ ///
+ public enum SkinArmorFlagsFlag : int
+ {
+ HELMET = 0, // 0x01
+ CHESTPLATE = 1, // 0x02
+ LEGGINGS = 2, // 0x04
+ BOOTS = 3, // 0x08
+ }
+}
diff --git a/PckStudio.Core/Skin/SkinArmorFlagsMask.cs b/PckStudio.Core/Skin/SkinArmorFlagsMask.cs
new file mode 100644
index 00000000..4dcf401e
--- /dev/null
+++ b/PckStudio.Core/Skin/SkinArmorFlagsMask.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PckStudio.Core.Skin
+{
+ [Flags]
+ public enum SkinArmorFlagsMask : int
+ {
+ NONE = 0, // 0x00
+ HELMET = 1 << 0, // 0x01, Makes the SkinBOX invisible when wearing a helmet or other head armor
+ CHESTPLATE = 1 << 1, // 0x02, Makes the SkinBOX invisible when wearing a chestplate; automatically applied with BODYARMOR, ARMARMOR0, ARMARMOR1, and BELT box types
+ LEGGINGS = 1 << 2, // 0x04, Makes the SkinBOX invisible when wearing leggings; automatically applied with BELT, LEGGING0, LEGGING1, SOCK0, and SOCK1 box types
+ BOOTS = 1 << 3, // 0x08, Makes the SkinBOX invisible when wearing boots; automatically applied with BOOT0, and BOOT1 box types
+ }
+}
diff --git a/PckStudio.Core/Skin/SkinBOX.cs b/PckStudio.Core/Skin/SkinBOX.cs
index 963e8517..5b33aba8 100644
--- a/PckStudio.Core/Skin/SkinBOX.cs
+++ b/PckStudio.Core/Skin/SkinBOX.cs
@@ -90,7 +90,7 @@ namespace PckStudio.Core.Skin
public Vector3 Pos { get; }
public Vector3 Size { get; }
public Vector2 UV { get; }
- public int ArmorMaskFlags { get; }
+ public SkinArmorFlags ArmorMaskFlags { get; }
public bool Mirror { get; }
public float Scale { get; }
// Simplified display info for the CustomSkinEditor
@@ -106,7 +106,7 @@ namespace PckStudio.Core.Skin
Pos = pos;
Size = size;
UV = uv;
- ArmorMaskFlags = armorMaskFlags;
+ ArmorMaskFlags = new SkinArmorFlags(armorMaskFlags);
Mirror = mirror;
Scale = scale;
}
diff --git a/PckStudio.ModelSupport/SkinModelImporter.cs b/PckStudio.ModelSupport/SkinModelImporter.cs
index 46cfc318..59f71ace 100644
--- a/PckStudio.ModelSupport/SkinModelImporter.cs
+++ b/PckStudio.ModelSupport/SkinModelImporter.cs
@@ -123,7 +123,7 @@ namespace PckStudio.ModelSupport
SkinBOX ApplyOffset(SkinBOX box)
{
SkinPartOffset offset = skinModel.PartOffsets.FirstOrDefault(offset => offset.Type == (box.IsOverlayPart() ? box.GetBaseType() : box.Type));
- return string.IsNullOrEmpty(offset.Type) ? box : new SkinBOX(box.Type, box.Pos - (Vector3.UnitY * offset.Value), box.Size, box.UV, box.ArmorMaskFlags, box.Mirror, box.Scale);
+ return string.IsNullOrEmpty(offset.Type) ? box : new SkinBOX(box.Type, box.Pos - (Vector3.UnitY * offset.Value), box.Size, box.UV, box.ArmorMaskFlags.ToValue(), box.Mirror, box.Scale);
}
IEnumerable convertedBoxes = boxes.Select(ApplyOffset);
diff --git a/PckStudio.Rendering/Extension/SkinBoxExtension.cs b/PckStudio.Rendering/Extension/SkinBoxExtension.cs
index 9eca892a..3286a80f 100644
--- a/PckStudio.Rendering/Extension/SkinBoxExtension.cs
+++ b/PckStudio.Rendering/Extension/SkinBoxExtension.cs
@@ -13,6 +13,36 @@ namespace PckStudio.Rendering.Extension
public static Cube ToCube(this SkinBOX skinBOX) => skinBOX.ToCube(0f);
public static Cube ToCube(this SkinBOX skinBOX, float inflate, bool flipZMapping = false)
- => new Cube(skinBOX.Pos.ToOpenTKVector(), skinBOX.Size.ToOpenTKVector(), skinBOX.UV.ToOpenTKVector(), skinBOX.Scale + inflate, skinBOX.Mirror, flipZMapping, skinBOX.ArmorMaskFlags);
+ {
+ SkinArmorFlags ArmorFlags = skinBOX.ArmorMaskFlags;
+
+ // this is to ensure armor types include the flags on top of custom defined flags for armor masking
+
+ switch(skinBOX.Type)
+ {
+ case "BODYARMOR":
+ case "ARMARMOR0":
+ case "ARMARMOR1":
+ ArmorFlags |= SkinArmorFlags.ChestplateAndArmArmor;
+ break;
+ case "LEGGING0":
+ case "LEGGING1":
+ ArmorFlags |= SkinArmorFlags.Leggings;
+ break;
+ case "BOOT0":
+ case "BOOT1":
+ ArmorFlags |= SkinArmorFlags.Boots;
+ break;
+ case "BELT":
+ ArmorFlags |= SkinArmorFlags.Belt;
+ break;
+ case "SOCK0":
+ case "SOCK1":
+ ArmorFlags |= SkinArmorFlags.Socks;
+ break;
+ }
+
+ return new Cube(skinBOX.Pos.ToOpenTKVector(), skinBOX.Size.ToOpenTKVector(), skinBOX.UV.ToOpenTKVector(), skinBOX.Scale + inflate, skinBOX.Mirror, flipZMapping, ArmorFlags.ToValue());
+ }
}
}