From 075c662e1a8634277d5a3268caa3877d893b0ce5 Mon Sep 17 00:00:00 2001 From: miku-666 <74728189+NessieHax@users.noreply.github.com> Date: Fri, 8 Mar 2024 12:31:28 +0100 Subject: [PATCH] Add Skin.cs and utilities to Serialize and Deserialize them --- .../Extensions/PckFileDataExtensions.cs | 113 ++++++++++-- PCK-Studio/Extensions/SkinExtensions.cs | 65 +++++++ .../Forms/Editor/CustomSkinEditor.Designer.cs | 2 +- PCK-Studio/Forms/Editor/CustomSkinEditor.cs | 80 +++++---- .../Skins-And-Textures/AddNewSkin.Designer.cs | 9 +- .../Forms/Skins-And-Textures/AddNewSkin.cs | 165 ++++++------------ .../Forms/Skins-And-Textures/AddNewSkin.resx | 15 -- .../Forms/Skins-And-Textures/SkinPreview.cs | 6 +- PCK-Studio/Internal/Skin.cs | 32 ++++ PCK-Studio/Internal/SkinANIM.cs | 4 + PCK-Studio/Internal/SkinPartOffset.cs | 2 +- PCK-Studio/MainForm.cs | 65 +++---- PCK-Studio/PckStudio.csproj | 2 + 13 files changed, 350 insertions(+), 210 deletions(-) create mode 100644 PCK-Studio/Extensions/SkinExtensions.cs create mode 100644 PCK-Studio/Internal/Skin.cs diff --git a/PCK-Studio/Extensions/PckFileDataExtensions.cs b/PCK-Studio/Extensions/PckFileDataExtensions.cs index 9488fdcb..5adaa1b0 100644 --- a/PCK-Studio/Extensions/PckFileDataExtensions.cs +++ b/PCK-Studio/Extensions/PckFileDataExtensions.cs @@ -7,8 +7,10 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using OMI.Formats.Languages; using OMI.Formats.Pck; using OMI.Workers; +using PckStudio.Internal; namespace PckStudio.Extensions { @@ -26,18 +28,107 @@ namespace PckStudio.Extensions { throw new Exception("File is not suitable to contain image data."); } - using (var stream = new MemoryStream(file.Data)) + using var stream = new MemoryStream(file.Data); + + try { - try - { - return Image.FromStream(stream); - } - catch(Exception ex) - { - Trace.WriteLine($"Failed to read image from pck file data({file.Filename}).", category: nameof(PckFileDataExtensions) + "." + nameof(GetTexture)); - Debug.WriteLine(ex.Message); - return EmptyImage; - } + return Image.FromStream(stream); + } + catch (Exception ex) + { + Trace.TraceError($"Failed to read image from pck file data({file.Filename})."); + Debug.WriteLine(ex.Message); + return EmptyImage; + } + } + + /// + /// Tries to get the skin id of the skin + /// + /// + /// Non-zero base number on success, otherwise 0 + /// + internal static int GetSkinId(this PckFileData file) + { + if (file.Filetype != PckFileType.SkinFile) + throw new InvalidOperationException("File is not a skin file"); + + string filename = Path.GetFileNameWithoutExtension(file.Filename); + if (!filename.StartsWith("dlcskin")) + { + Trace.TraceWarning($"[{nameof(GetSkin)}] File does not start with 'dlcskin'"); + return 0; + } + + int skinId = 0; + if (!int.TryParse(filename.Substring("dlcskin".Length), out skinId)) + { + Trace.TraceWarning($"[{nameof(GetSkin)}] Failed to parse Skin Id"); + } + return skinId; + } + + internal static Skin GetSkin(this PckFileData file) + { + if (file.Filetype != PckFileType.SkinFile) + throw new InvalidOperationException("File is not a skin file"); + + if (file.Properties.Contains("CAPEPATH")) + Debug.WriteLine($"[{nameof(GetSkin)}] TODO: add cape texture/path."); + + int skinId = file.GetSkinId(); + + string name = file.Properties.GetPropertyValue("DISPLAYNAME"); + Image texture = file.GetTexture(); + SkinANIM anim = file.Properties.GetPropertyValue("ANIM", SkinANIM.FromString); + IEnumerable boxes = file.Properties.GetProperties("BOX").Select(kv => SkinBOX.FromString(kv.Value)); + IEnumerable offsets = file.Properties.GetProperties("OFFSET").Select(kv => SkinPartOffset.FromString(kv.Value)); + return new Skin(name, skinId, texture, anim, boxes, offsets); + } + + internal static void SetSkin(this PckFileData file, Skin skin, LOCFile localizationFile) + { + if (file.Filetype != PckFileType.SkinFile) + throw new InvalidOperationException("File is not a skin file"); + + file.SetData(skin.Texture, ImageFormat.Png); + + string skinId = skin.Id.ToString("d08"); + + // TODO: keep filepath + file.Filename = $"dlcskin{skinId}.png"; + + string skinLocKey = $"IDS_dlcskin{skinId}_DISPLAYNAME"; + file.Properties.SetProperty("DISPLAYNAME", skin.Name); + file.Properties.SetProperty("DISPLAYNAMEID", skinLocKey); + localizationFile.AddLocKey(skinLocKey, skin.Name); + + if (!string.IsNullOrEmpty(skin.Theme)) + { + file.Properties.SetProperty("THEMENAME", skin.Theme); + file.Properties.SetProperty("THEMENAMEID", $"IDS_dlcskin{skinId}_THEMENAME"); + localizationFile.AddLocKey($"IDS_dlcskin{skinId}_THEMENAME", skin.Theme); + } + + if (skin.HasCape) + { + file.Properties.SetProperty("CAPEPATH", $"dlccape{skinId}.png"); + } + + file.Properties.SetProperty("ANIM", skin.ANIM.ToString()); + file.Properties.SetProperty("GAME_FLAGS", "0x18"); + file.Properties.SetProperty("FREE", "1"); + + file.Properties.RemoveAll(kv => kv.Key == "BOX"); + file.Properties.RemoveAll(kv => kv.Key == "OFFSET"); + + foreach (SkinBOX box in skin.AdditionalBoxes) + { + file.Properties.Add(box.ToProperty()); + } + foreach (SkinPartOffset offset in skin.PartOffsets) + { + file.Properties.Add(offset.ToProperty()); } } diff --git a/PCK-Studio/Extensions/SkinExtensions.cs b/PCK-Studio/Extensions/SkinExtensions.cs new file mode 100644 index 00000000..e26dedba --- /dev/null +++ b/PCK-Studio/Extensions/SkinExtensions.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Drawing.Imaging; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OMI.Formats.Languages; +using OMI.Formats.Pck; +using PckStudio.Internal; + +namespace PckStudio.Extensions +{ + internal static class SkinExtensions + { + public static PckFileData CreateFile(this Skin skin, LOCFile localizationFile) + { + string skinId = skin.Id.ToString("d08"); + PckFileData skinFile = new PckFileData($"dlcskin{skinId}.png", PckFileType.SkinFile); + + string skinLocKey = $"IDS_dlcskin{skinId}_DISPLAYNAME"; + skinFile.Properties.Add("DISPLAYNAME", skin.Name); + skinFile.Properties.Add("DISPLAYNAMEID", skinLocKey); + localizationFile.AddLocKey(skinLocKey, skin.Name); + + if (!string.IsNullOrEmpty(skin.Theme)) + { + skinFile.Properties.Add("THEMENAME", skin.Theme); + skinFile.Properties.Add("THEMENAMEID", $"IDS_dlcskin{skinId}_THEMENAME"); + localizationFile.AddLocKey($"IDS_dlcskin{skinId}_THEMENAME", skin.Theme); + } + + if (skin.HasCape) + { + skinFile.Properties.Add("CAPEPATH", $"dlccape{skinId}.png"); + } + + skinFile.Properties.Add("ANIM", skin.ANIM); + skinFile.Properties.Add("GAME_FLAGS", "0x18"); + skinFile.Properties.Add("FREE", "1"); + + foreach (SkinBOX box in skin.AdditionalBoxes) + { + skinFile.Properties.Add(box.ToProperty()); + } + foreach (SkinPartOffset offset in skin.PartOffsets) + { + skinFile.Properties.Add(offset.ToProperty()); + } + + skinFile.SetData(skin.Texture, ImageFormat.Png); + + return skinFile; + } + + public static PckFileData CreateCapeFile(this Skin skin) + { + if (!skin.HasCape) + throw new InvalidOperationException("Skin does not contain a cape."); + string skinId = skin.Id.ToString("d08"); + PckFileData capeFile = new PckFileData($"dlccape{skinId}.png", PckFileType.CapeFile); + capeFile.SetData(skin.CapeTexture, ImageFormat.Png); + return capeFile; + } + } +} diff --git a/PCK-Studio/Forms/Editor/CustomSkinEditor.Designer.cs b/PCK-Studio/Forms/Editor/CustomSkinEditor.Designer.cs index 55f32a05..686829b3 100644 --- a/PCK-Studio/Forms/Editor/CustomSkinEditor.Designer.cs +++ b/PCK-Studio/Forms/Editor/CustomSkinEditor.Designer.cs @@ -437,7 +437,7 @@ this.Name = "CustomSkinEditor"; this.Style = MetroFramework.MetroColorStyle.Silver; this.Theme = MetroFramework.MetroThemeStyle.Dark; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.generateModel_FormClosing); + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.CustomSkinEditor_FormClosing); this.contextMenuStrip1.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.SizeXUpDown)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.SizeYUpDown)).EndInit(); diff --git a/PCK-Studio/Forms/Editor/CustomSkinEditor.cs b/PCK-Studio/Forms/Editor/CustomSkinEditor.cs index ee67a740..32ef3e4c 100644 --- a/PCK-Studio/Forms/Editor/CustomSkinEditor.cs +++ b/PCK-Studio/Forms/Editor/CustomSkinEditor.cs @@ -21,10 +21,12 @@ namespace PckStudio.Forms.Editor { public partial class CustomSkinEditor : MetroForm { - private Image _previewImage; public Image PreviewImage => _previewImage; - private PckFileData _file; + public Skin ResultSkin => _skin; + + private Image _previewImage; + private Skin _skin; private Random rng; private BindingSource skinPartListBindingSource; @@ -36,35 +38,45 @@ namespace PckStudio.Forms.Editor PixelOffsetMode = PixelOffsetMode.HighQuality, }; - public CustomSkinEditor(PckFileData file) + private CustomSkinEditor() { InitializeComponent(); - _file = file; rng = new Random(); } + public CustomSkinEditor(Skin skin) : this() + { + _skin = skin; + } + protected override void OnLoad(EventArgs e) { base.OnLoad(e); renderer3D1.InitializeGL(); - renderer3D1.OutlineColor = Color.DarkSlateBlue; - if (_file.Size > 0) + if (_skin.Texture is not null) { - renderer3D1.Texture = _file.GetTexture(); + renderer3D1.Texture = _skin.Texture; } - LoadModelData(_file.Properties); + renderer3D1.ANIM = _skin.ANIM; + renderer3D1.OutlineColor = Color.DarkSlateBlue; + LoadModelData(); } - private void LoadModelData(PckFileProperties properties) + private void LoadModelData() { - renderer3D1.ANIM = properties.GetPropertyValue("ANIM", SkinANIM.FromString); - var boxProperties = properties.GetProperties("BOX"); - var offsetProperties = properties.GetProperties("OFFSET"); + var boxProperties = _skin.AdditionalBoxes; + var offsetProperties = _skin.PartOffsets; - skinNameLabel.Text = properties.HasProperty("DISPLAYNAME") ? properties.GetPropertyValue("DISPLAYNAME") : ""; + skinNameLabel.Text = _skin.Name; - Array.ForEach(boxProperties, kv => renderer3D1.ModelData.Add(SkinBOX.FromString(kv.Value))); - Array.ForEach(offsetProperties, kv => renderer3D1.SetPartOffset(SkinPartOffset.FromString(kv.Value))); + foreach (SkinBOX box in boxProperties) + { + renderer3D1.ModelData.Add(box); + } + foreach (SkinPartOffset offset in offsetProperties) + { + renderer3D1.SetPartOffset(offset); + } skinPartListBindingSource = new BindingSource(renderer3D1.ModelData, null); skinPartListBox.DataSource = skinPartListBindingSource; @@ -128,22 +140,28 @@ namespace PckStudio.Forms.Editor private void buttonDone_Click(object sender, EventArgs e) { - _file.Properties.RemoveAll(kv => kv.Key == "BOX"); - foreach (var part in renderer3D1.ModelData) - { - _file.Properties.Add("BOX", part); - } - _previewImage = renderer3D1.GetThumbnail(); + //Debug.Fail("TODO: Implement"); + _skin.AdditionalBoxes.Clear(); + _skin.AdditionalBoxes.AddRange(renderer3D1.ModelData); + + // TODO: Get part offset list/IEnumerable from renderer + //_skin.PartOffsets.Clear(); + //_skin.PartOffsets.AddRange(); + + //_previewImage = renderer3D1.GetThumbnail(); DialogResult = DialogResult.OK; } + // TODO private void buttonExportModel_Click(object sender, EventArgs e) { - //SaveFileDialog saveFileDialog = new SaveFileDialog(); - //saveFileDialog.Filter = "Custom Skin Model File | *.CSM"; - //if (saveFileDialog.ShowDialog() != DialogResult.OK) - // return; + SaveFileDialog saveFileDialog = new SaveFileDialog(); + saveFileDialog.Title = "Save Model File"; + saveFileDialog.Filter = "Custom Skin Model File (*.csm,*.CSM)|*.csm;*.CSM|" + + "Custom Skin Model Binary File (*.csmb)|*.csmb|"; + if (saveFileDialog.ShowDialog() != DialogResult.OK) + return; //string contents = ""; //foreach (ListViewItem listViewItem in listViewBoxes.Items) //{ @@ -159,12 +177,13 @@ namespace PckStudio.Forms.Editor //File.WriteAllText(saveFileDialog.FileName, contents); } - [Obsolete("Kept for backwards compatibility, remove later.")] + [Obsolete("Kept for backwards compatibility.")] private void importCustomSkinButton_Click(object sender, EventArgs e) { OpenFileDialog openFileDialog = new OpenFileDialog(); - openFileDialog.Filter = "Custom Skin Model File (*.csm,*.CSM)|*.csm;*.CSM|Custom Skin Model Binary File (*.csmb)|*.csmb|JSON Model File(*.json)|*.JSON;*.json"; openFileDialog.Title = "Select Model File"; + openFileDialog.Filter = "Custom Skin Model File (*.csm,*.CSM)|*.csm;*.CSM|" + + "Custom Skin Model Binary File (*.csmb)|*.csmb|"; if (MessageBox.Show("Import custom model project file? Your current work will be lost!", "", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1) == DialogResult.Yes && openFileDialog.ShowDialog() == DialogResult.OK) { string fileExtension = Path.GetExtension(openFileDialog.FileName); @@ -210,9 +229,8 @@ namespace PckStudio.Forms.Editor } } - private void generateModel_FormClosing(object sender, FormClosingEventArgs e) - { - + private void CustomSkinEditor_FormClosing(object sender, FormClosingEventArgs e) + { } private void outlineColorButton_Click(object sender, EventArgs e) @@ -264,7 +282,7 @@ namespace PckStudio.Forms.Editor return; } generateTextureCheckBox.Checked = false; - uvPictureBox.BackgroundImage = img; + uvPictureBox.BackgroundImage = _skin.Texture = img; } private void skinPartListBox_DoubleClick(object sender, EventArgs e) diff --git a/PCK-Studio/Forms/Skins-And-Textures/AddNewSkin.Designer.cs b/PCK-Studio/Forms/Skins-And-Textures/AddNewSkin.Designer.cs index 871ee322..fdfdce3f 100644 --- a/PCK-Studio/Forms/Skins-And-Textures/AddNewSkin.Designer.cs +++ b/PCK-Studio/Forms/Skins-And-Textures/AddNewSkin.Designer.cs @@ -33,7 +33,6 @@ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AddNewSkin)); System.Windows.Forms.Label label2; System.Windows.Forms.Label label1; - this.textTheme = new System.Windows.Forms.TextBox(); this.contextMenuSkin = new System.Windows.Forms.ContextMenuStrip(this.components); this.replaceToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.contextMenuCape = new System.Windows.Forms.ContextMenuStrip(this.components); @@ -81,11 +80,6 @@ label1.ForeColor = System.Drawing.Color.White; label1.Name = "label1"; // - // textTheme - // - resources.ApplyResources(this.textTheme, "textTheme"); - this.textTheme.Name = "textTheme"; - // // contextMenuSkin // this.contextMenuSkin.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -289,6 +283,7 @@ // capePictureBox // resources.ApplyResources(this.capePictureBox, "capePictureBox"); + this.capePictureBox.BackgroundInterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Default; this.capePictureBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.capePictureBox.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; this.capePictureBox.Name = "capePictureBox"; @@ -298,6 +293,7 @@ // skinPictureBox // resources.ApplyResources(this.skinPictureBox, "skinPictureBox"); + this.skinPictureBox.BackgroundInterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Default; this.skinPictureBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.skinPictureBox.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; this.skinPictureBox.Name = "skinPictureBox"; @@ -343,7 +339,6 @@ } #endregion - private System.Windows.Forms.TextBox textTheme; private System.Windows.Forms.ContextMenuStrip contextMenuSkin; private System.Windows.Forms.ToolStripMenuItem replaceToolStripMenuItem; private System.Windows.Forms.ContextMenuStrip contextMenuCape; diff --git a/PCK-Studio/Forms/Skins-And-Textures/AddNewSkin.cs b/PCK-Studio/Forms/Skins-And-Textures/AddNewSkin.cs index 36ab7f02..eb6e0e1c 100644 --- a/PCK-Studio/Forms/Skins-And-Textures/AddNewSkin.cs +++ b/PCK-Studio/Forms/Skins-And-Textures/AddNewSkin.cs @@ -12,123 +12,93 @@ using PckStudio.IO._3DST; using PckStudio.Properties; using PckStudio.Forms; using PckStudio.Extensions; +using System.Linq; +using System.Diagnostics; namespace PckStudio.Popups { public partial class AddNewSkin : MetroFramework.Forms.MetroForm { - public PckFileData SkinFile => skin; - public PckFileData CapeFile => cape; - public bool HasCape => cape is not null; + public Skin NewSkin => newSkin; + + //public PckFileData SkinFile => _skinFile; + //public PckFileData CapeFile => cape; + //public bool HasCape => cape is not null; private LOCFile currentLoc; - private PckFileData skin = new PckFileData("dlcskinXYXYXYXY", PckFileType.SkinFile); - private PckFileData cape; - private SkinANIM anim = new SkinANIM(); + //private PckFileData _skinFile = new PckFileData("dlcskinXYXYXYXY", PckFileType.SkinFile); + //private PckFileData cape; + private Skin newSkin; private Random rng = new Random(); - private eSkinType skinType; - - private enum eSkinType - { - Invalid = -1, - _64x64, - _64x32, - _64x64HD, - _64x32HD, - Custom, - } - public AddNewSkin(LOCFile loc) { InitializeComponent(); currentLoc = loc; + newSkin = new Skin("", 0, Resources.classic_template, new SkinANIM(), Enumerable.Empty(), Enumerable.Empty()); } - private void CheckImage(Image img) + private void SetNewTexture(Image img) { - switch (img.Height) + if (img is null) { - case 64: - anim.SetFlag(SkinAnimFlag.RESOLUTION_64x64, true); - MessageBox.Show("64x64 Skin Detected"); - skinType = eSkinType._64x64; - break; - case 32: - anim.SetFlag(SkinAnimFlag.RESOLUTION_64x64 | SkinAnimFlag.SLIM_MODEL, false); - MessageBox.Show("64x32 Skin Detected"); - skinType = eSkinType._64x32; - break; - default: - if (img.Width == img.Height) - { - anim.SetFlag(SkinAnimFlag.RESOLUTION_64x64, true); - MessageBox.Show("64x64 HD Skin Detected"); - skinType = eSkinType._64x64HD; - break; - } - - if (img.Height == img.Width / 2) - { - anim.SetFlag(SkinAnimFlag.RESOLUTION_64x64 | SkinAnimFlag.SLIM_MODEL, false); - MessageBox.Show("64x32 HD Skin Detected"); - skinType = eSkinType._64x32HD; - break; - } - - MessageBox.Show("Not a Valid Skin File"); - skinType = eSkinType.Invalid; - return; + Debug.Assert(false, "Image is null."); } + if (img.Width != img.Height && img.Height != img.Width / 2) + { + MessageBox.Show("The selected image does not suit a skin texture.", "Invalid image dimensions.", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + newSkin.ANIM.SetFlag(SkinAnimFlag.RESOLUTION_64x64, img.Width == img.Height); - skinPictureBox.Image = img; - capePictureBox.Visible = true; - buttonCape.Visible = true; - capeLabel.Visible = true; - buttonDone.Enabled = true; - buttonAnimGen.Enabled = true; + skinPictureBox.Image = newSkin.Texture = img; labelSelectTexture.Visible = false; + //capePictureBox.Visible = true; + //buttonCape.Visible = true; + //capeLabel.Visible = true; + //buttonDone.Enabled = true; + //buttonAnimGen.Enabled = true; } private void DrawModel() { - bool isSlim = anim.GetFlag(SkinAnimFlag.SLIM_MODEL); + bool isSlim = newSkin.ANIM.GetFlag(SkinAnimFlag.SLIM_MODEL); Pen outlineColor = Pens.LightGray; Brush fillColor = Brushes.Gray; Image previewTexture = new Bitmap(displayBox.Width, displayBox.Height); using (Graphics g = Graphics.FromImage(previewTexture)) { - if(!anim.GetFlag(SkinAnimFlag.HEAD_DISABLED)) + if(!newSkin.ANIM.GetFlag(SkinAnimFlag.HEAD_DISABLED)) { //Head g.DrawRectangle(outlineColor, 70, 15, 40, 40); g.FillRectangle(fillColor, 71, 16, 39, 39); } - if (!anim.GetFlag(SkinAnimFlag.BODY_DISABLED)) + if (!newSkin.ANIM.GetFlag(SkinAnimFlag.BODY_DISABLED)) { //Body g.DrawRectangle(outlineColor, 70, 55, 40, 60); g.FillRectangle(fillColor, 71, 56, 39, 59); } - if (!anim.GetFlag(SkinAnimFlag.RIGHT_ARM_DISABLED)) + if (!newSkin.ANIM.GetFlag(SkinAnimFlag.RIGHT_ARM_DISABLED)) { //Arm0 g.DrawRectangle(outlineColor, isSlim ? 55 : 50, 55, isSlim ? 15 : 20, 60); g.FillRectangle(fillColor , isSlim ? 56 : 51, 56, isSlim ? 14 : 19, 59); } - if (!anim.GetFlag(SkinAnimFlag.LEFT_ARM_DISABLED)) + if (!newSkin.ANIM.GetFlag(SkinAnimFlag.LEFT_ARM_DISABLED)) { //Arm1 g.DrawRectangle(outlineColor, 110, 55, isSlim ? 15 : 20, 60); g.FillRectangle(fillColor, 111, 56, isSlim ? 14 : 19, 59); } - if (!anim.GetFlag(SkinAnimFlag.RIGHT_LEG_DISABLED)) + if (!newSkin.ANIM.GetFlag(SkinAnimFlag.RIGHT_LEG_DISABLED)) { //Leg0 g.DrawRectangle(outlineColor, 70, 115, 20, 60); g.FillRectangle(fillColor, 71, 116, 19, 59); } - if (!anim.GetFlag(SkinAnimFlag.LEFT_LEG_DISABLED)) + if (!newSkin.ANIM.GetFlag(SkinAnimFlag.LEFT_LEG_DISABLED)) { //Leg1 g.DrawRectangle(outlineColor, 90, 115, 20, 60); @@ -158,7 +128,7 @@ namespace PckStudio.Popups OpenFileDialog ofd = new OpenFileDialog(); if (ofd.ShowDialog() == DialogResult.OK) { - CheckImage(Image.FromFile(ofd.FileName)); + SetNewTexture(Image.FromFile(ofd.FileName)); } } @@ -187,12 +157,12 @@ namespace PckStudio.Popups using (var fs = File.OpenRead(ofd.FileName)) { var reader = new _3DSTextureReader(); - CheckImage(reader.FromStream(fs)); + SetNewTexture(reader.FromStream(fs)); } textSkinName.Text = Path.GetFileNameWithoutExtension(ofd.FileName); return; } - CheckImage(Image.FromFile(ofd.FileName)); + SetNewTexture(Image.FromFile(ofd.FileName)); } } } @@ -223,9 +193,7 @@ namespace PckStudio.Popups MessageBox.Show("Not a Valid Cape File"); return; } - capePictureBox.Image = Image.FromFile(ofd.FileName); - cape ??= new PckFileData("dlccapeXYXYXYXY", PckFileType.CapeFile); - cape.SetData(File.ReadAllBytes(ofd.FileName)); + newSkin.CapeTexture = capePictureBox.Image = Image.FromFile(ofd.FileName); contextMenuCape.Items[0].Text = "Replace"; capeLabel.Visible = false; contextMenuCape.Visible = true; @@ -235,35 +203,18 @@ namespace PckStudio.Popups private void CreateButton_Click(object sender, EventArgs e) { - if (!int.TryParse(textSkinID.Text, out int _skinId)) + if (radioButtonManual.Checked) { - MessageBox.Show("The Skin ID Must be a Unique 8 Digit Number Thats Not Already in Use", "Invalid Skin ID", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; + if (!int.TryParse(textSkinID.Text, out int _skinId)) + { + MessageBox.Show("The Skin Id must be a unique 8 digit number that is not already in use", "Invalid Skin Id", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + newSkin.Id = _skinId; } - string skinId = _skinId.ToString("d08"); - skin.Filename = $"dlcskin{skinId}.png"; - string skinDisplayNameLocKey = $"IDS_dlcskin{skinId}_DISPLAYNAME"; - currentLoc.AddLocKey(skinDisplayNameLocKey, textSkinName.Text); - skin.Properties.Add("DISPLAYNAME", textSkinName.Text); - skin.Properties.Add("DISPLAYNAMEID", skinDisplayNameLocKey); - if (!string.IsNullOrEmpty(textThemeName.Text)) - { - skin.Properties.Add("THEMENAME", textThemeName.Text); - skin.Properties.Add("THEMENAMEID", $"IDS_dlcskin{skinId}_THEMENAME"); - currentLoc.AddLocKey($"IDS_dlcskin{skinId}_THEMENAME", textThemeName.Text); - } - skin.Properties.Add("ANIM", anim); - skin.Properties.Add("GAME_FLAGS", "0x18"); - skin.Properties.Add("FREE", "1"); - - if (HasCape) - { - cape.Filename = $"dlccape{skinId}.png"; - skin.Properties.Add("CAPEPATH", cape.Filename); - } - skin.SetData(skinPictureBox.Image, ImageFormat.Png); + newSkin.Name = textSkinName.Text; + newSkin.Theme = textThemeName.Text; DialogResult = DialogResult.OK; - Close(); } private void textSkinID_TextChanged(object sender, EventArgs e) @@ -274,24 +225,18 @@ namespace PckStudio.Popups private void CreateCustomModel_Click(object sender, EventArgs e) { - //Prompt for skin model generator if (MessageBox.Show("Create your own custom skin model?", "", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1) != DialogResult.Yes) return; - skin.SetData(Resources.classic_template, ImageFormat.Png); + using CustomSkinEditor customSkinEditor = new CustomSkinEditor(newSkin); - using CustomSkinEditor generate = new CustomSkinEditor(skin); - - if (generate.ShowDialog() == DialogResult.OK) + if (customSkinEditor.ShowDialog() == DialogResult.OK) { - displayBox.Image = generate.PreviewImage; + skinPictureBox.Image = customSkinEditor.ResultSkin.Texture; + newSkin = customSkinEditor.ResultSkin; buttonDone.Enabled = true; labelSelectTexture.Visible = false; - if (skinType != eSkinType._64x64 && skinType != eSkinType._64x64HD) - { - buttonSkin.Location = new Point(buttonSkin.Location.X - skinPictureBox.Width, buttonSkin.Location.Y); - skinType = eSkinType._64x64; - } + DrawModel(); } } @@ -299,8 +244,8 @@ namespace PckStudio.Popups { if (radioButtonAuto.Checked) { - int num = rng.Next(100000, 99999999); - textSkinID.Text = num.ToString(); + newSkin.Id = rng.Next(100000, 99999999); + textSkinID.Text = newSkin.Id.ToString(); textSkinID.Enabled = false; } } @@ -312,10 +257,10 @@ namespace PckStudio.Popups private void buttonAnimGen_Click(object sender, EventArgs e) { - using ANIMEditor diag = new ANIMEditor(anim.ToString()); + using ANIMEditor diag = new ANIMEditor(newSkin.ANIM); if (diag.ShowDialog(this) == DialogResult.OK) { - anim = diag.ResultAnim; + newSkin.ANIM = diag.ResultAnim; DrawModel(); } } diff --git a/PCK-Studio/Forms/Skins-And-Textures/AddNewSkin.resx b/PCK-Studio/Forms/Skins-And-Textures/AddNewSkin.resx index 2f724c3d..2310be6d 100644 --- a/PCK-Studio/Forms/Skins-And-Textures/AddNewSkin.resx +++ b/PCK-Studio/Forms/Skins-And-Textures/AddNewSkin.resx @@ -219,21 +219,6 @@ 19 - - 102, 78 - - - 239, 20 - - - 32 - - - textTheme - - - System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 17, 17 diff --git a/PCK-Studio/Forms/Skins-And-Textures/SkinPreview.cs b/PCK-Studio/Forms/Skins-And-Textures/SkinPreview.cs index c13fed89..68108ddd 100644 --- a/PCK-Studio/Forms/Skins-And-Textures/SkinPreview.cs +++ b/PCK-Studio/Forms/Skins-And-Textures/SkinPreview.cs @@ -18,11 +18,11 @@ namespace PckStudio.Forms private Image texture; private IEnumerable data; - public SkinPreview(Image image, IEnumerable modelData) + public SkinPreview(Skin skin) { InitializeComponent(); - texture = image; - data = modelData; + texture = skin.Texture; + data = skin.AdditionalBoxes; } protected override void OnLoad(EventArgs e) diff --git a/PCK-Studio/Internal/Skin.cs b/PCK-Studio/Internal/Skin.cs new file mode 100644 index 00000000..12dc0ca1 --- /dev/null +++ b/PCK-Studio/Internal/Skin.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using OMI.Formats.Pck; + +namespace PckStudio.Internal +{ + public sealed class Skin + { + public string Name { get; set; } + public string Theme { get; set; } + public int Id { get; set; } + public Image Texture { get; set; } + public Image CapeTexture { get; set; } + public bool HasCape => CapeTexture is not null; + public SkinANIM ANIM { get; set; } + public List AdditionalBoxes { get; } + public List PartOffsets { get; } + + public Skin(string name, int id, Image texture, SkinANIM anim, IEnumerable additionalBoxes, IEnumerable partOffsets) + { + Name = name; + Id = id; + Texture = texture; + ANIM = anim; + AdditionalBoxes = new List(additionalBoxes); + PartOffsets = new List(partOffsets); + } + } +} diff --git a/PCK-Studio/Internal/SkinANIM.cs b/PCK-Studio/Internal/SkinANIM.cs index c152efa8..90c7c6b8 100644 --- a/PCK-Studio/Internal/SkinANIM.cs +++ b/PCK-Studio/Internal/SkinANIM.cs @@ -57,6 +57,10 @@ namespace PckStudio.Internal => IsValidANIM(value) ? new SkinANIM(Convert.ToInt32(value.TrimEnd(' ', '\n', '\r'), 16)) : new SkinANIM(); + + public static SkinANIM FromValue(int value) => new SkinANIM(value); + + public int ToValue() => _flags.Data; public static SkinANIM operator |(SkinANIM _this, SkinANIM other) => new SkinANIM(_this._flags.Data | other._flags.Data); diff --git a/PCK-Studio/Internal/SkinPartOffset.cs b/PCK-Studio/Internal/SkinPartOffset.cs index f651f4ff..c6c34536 100644 --- a/PCK-Studio/Internal/SkinPartOffset.cs +++ b/PCK-Studio/Internal/SkinPartOffset.cs @@ -7,7 +7,7 @@ using System.Text.RegularExpressions; namespace PckStudio.Internal { - internal readonly struct SkinPartOffset + public readonly struct SkinPartOffset { private static readonly Regex sWhitespace = new Regex(@"\s+"); internal static string ReplaceWhitespace(string input, string replacement) diff --git a/PCK-Studio/MainForm.cs b/PCK-Studio/MainForm.cs index 70cb7c8f..301817ba 100644 --- a/PCK-Studio/MainForm.cs +++ b/PCK-Studio/MainForm.cs @@ -432,21 +432,17 @@ namespace PckStudio public void HandleSkinFile(PckFileData file) { - if (file.Properties.HasProperty("BOX")) + using CustomSkinEditor skinEditor = new CustomSkinEditor(file.GetSkin()); + if (skinEditor.ShowDialog() == DialogResult.OK) { - using CustomSkinEditor generate = new CustomSkinEditor(file); - if (generate.ShowDialog() == DialogResult.OK) - { - entryDataTextBox.Text = entryTypeTextBox.Text = string.Empty; - wasModified = true; - ReloadMetaTreeView(); - } - return; - } + if (!TryGetLocFile(out var locFile)) + Debug.Fail("Failed to aquire loc file."); + file.SetSkin(skinEditor.ResultSkin, locFile); - var skinPreview = new SkinPreview(file.GetTexture(), file.Properties.GetProperties("BOX").Select(kv => SkinBOX.FromString(kv.Value))); - skinPreview.ANIM = file.Properties.GetPropertyValue("ANIM", SkinANIM.FromString); - skinPreview.ShowDialog(); + entryDataTextBox.Text = entryTypeTextBox.Text = string.Empty; + wasModified = true; + ReloadMetaTreeView(); + } } public void HandleModelsFile(PckFileData file) @@ -783,43 +779,50 @@ namespace PckStudio MessageBox.Show("No .loc file found", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } - using (AddNewSkin add = new AddNewSkin(locFile)) - if (add.ShowDialog() == DialogResult.OK) + using (AddNewSkin addNewSkinDialog = new AddNewSkin(locFile)) + if (addNewSkinDialog.ShowDialog() == DialogResult.OK) { - - if (currentPCK.HasFile("Skins.pck", PckFileType.SkinDataFile)) // Prioritize Skins.pck + var skinFile = addNewSkinDialog.NewSkin.CreateFile(locFile); + currentPCK.AddFile(skinFile); + if (currentPCK.HasFile("Skins.pck", PckFileType.SkinDataFile)) // Prioritize Skins.pck { TreeNode subPCK = treeViewMain.Nodes.Find("Skins.pck", false).FirstOrDefault(); - if (subPCK.Nodes.ContainsKey("Skins")) add.SkinFile.Filename = add.SkinFile.Filename.Insert(0, "Skins/"); - add.SkinFile.Filename = add.SkinFile.Filename.Insert(0, "Skins.pck/"); - TreeNode newNode = new TreeNode(Path.GetFileName(add.SkinFile.Filename)); - newNode.Tag = add.SkinFile; + if (subPCK.Nodes.ContainsKey("Skins")) + skinFile.Filename = skinFile.Filename.Insert(0, "Skins/"); + skinFile.Filename = skinFile.Filename.Insert(0, "Skins.pck/"); + TreeNode newNode = new TreeNode(Path.GetFileName(skinFile.Filename)); + newNode.Tag = skinFile; SetNodeIcon(newNode, PckFileType.SkinFile); subPCK.Nodes.Add(newNode); RebuildSubPCK(newNode.FullPath); } else { - if (treeViewMain.Nodes.ContainsKey("Skins")) add.SkinFile.Filename = add.SkinFile.Filename.Insert(0, "Skins/"); // Then Skins folder - currentPCK.AddFile(add.SkinFile); + if (treeViewMain.Nodes.ContainsKey("Skins")) + skinFile.Filename = skinFile.Filename.Insert(0, "Skins/"); // Then Skins folder + currentPCK.AddFile(skinFile); } - if (add.HasCape) + + if (addNewSkinDialog.NewSkin.HasCape) { - if (currentPCK.HasFile("Skins.pck", PckFileType.SkinDataFile)) // Prioritize Skins.pck + var capeFile = addNewSkinDialog.NewSkin.CreateCapeFile(); + if (currentPCK.HasFile("Skins.pck", PckFileType.SkinDataFile)) // Prioritize Skins.pck { TreeNode subPCK = treeViewMain.Nodes.Find("Skins.pck", false).FirstOrDefault(); - if (subPCK.Nodes.ContainsKey("Skins")) add.CapeFile.Filename = add.CapeFile.Filename.Insert(0, "Skins/"); - add.CapeFile.Filename = add.CapeFile.Filename.Insert(0, "Skins.pck/"); - TreeNode newNode = new TreeNode(Path.GetFileName(add.CapeFile.Filename)); - newNode.Tag = add.CapeFile; + if (subPCK.Nodes.ContainsKey("Skins")) + capeFile.Filename = capeFile.Filename.Insert(0, "Skins/"); + capeFile.Filename = capeFile.Filename.Insert(0, "Skins.pck/"); + TreeNode newNode = new TreeNode(Path.GetFileName(capeFile.Filename)); + newNode.Tag = capeFile; SetNodeIcon(newNode, PckFileType.SkinFile); subPCK.Nodes.Add(newNode); RebuildSubPCK(newNode.FullPath); } else { - if (treeViewMain.Nodes.ContainsKey("Skins")) add.CapeFile.Filename = add.CapeFile.Filename.Insert(0, "Skins/"); // Then Skins folder - currentPCK.AddFile(add.CapeFile); + if (treeViewMain.Nodes.ContainsKey("Skins")) + capeFile.Filename = capeFile.Filename.Insert(0, "Skins/"); // Then Skins folder + currentPCK.AddFile(capeFile); } } diff --git a/PCK-Studio/PckStudio.csproj b/PCK-Studio/PckStudio.csproj index 04546dcb..25cb5cda 100644 --- a/PCK-Studio/PckStudio.csproj +++ b/PCK-Studio/PckStudio.csproj @@ -137,8 +137,10 @@ + +