From ec04639928845112e6efac0a5b6420e760e297d1 Mon Sep 17 00:00:00 2001 From: MayNL Date: Mon, 11 May 2026 18:10:06 -0400 Subject: [PATCH] Added SkinArmorFlags classes --- .../Controls/Editor/BoxEditorControl.cs | 8 +- PckStudio.Core/PckStudio.Core.csproj | 3 + PckStudio.Core/Skin/SkinArmorFlags.cs | 120 ++++++++++++++++++ PckStudio.Core/Skin/SkinArmorFlagsFlag.cs | 13 ++ PckStudio.Core/Skin/SkinArmorFlagsMask.cs | 18 +++ PckStudio.Core/Skin/SkinBOX.cs | 4 +- PckStudio.ModelSupport/SkinModelImporter.cs | 2 +- .../Extension/SkinBoxExtension.cs | 32 ++++- 8 files changed, 192 insertions(+), 8 deletions(-) create mode 100644 PckStudio.Core/Skin/SkinArmorFlags.cs create mode 100644 PckStudio.Core/Skin/SkinArmorFlagsFlag.cs create mode 100644 PckStudio.Core/Skin/SkinArmorFlagsMask.cs 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()); + } } }