From d5743bbd2fb284fde12bf5f01e1f011ec172a86d Mon Sep 17 00:00:00 2001 From: AsY!um- <377468+AsYlum-@users.noreply.github.com> Date: Fri, 1 May 2026 11:20:02 +0200 Subject: [PATCH 1/4] Update extract/pack all logic in UopPacker plugin. --- .../UserControls/UopPackerControl.Designer.cs | 95 ++++++++++++++++--- .../UserControls/UopPackerControl.cs | 75 +++++++++++++-- .../UserControls/UopPackerControl.resx | 2 +- 3 files changed, 149 insertions(+), 23 deletions(-) diff --git a/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.Designer.cs b/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.Designer.cs index 35f34c2..a973c60 100644 --- a/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.Designer.cs +++ b/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.Designer.cs @@ -82,6 +82,11 @@ private void InitializeComponent() toolStripStatusLabel2 = new System.Windows.Forms.ToolStripStatusLabel(); pack = new System.Windows.Forms.RadioButton(); extract = new System.Windows.Forms.RadioButton(); + packAllGumpCompressionLabel = new System.Windows.Forms.Label(); + packAllGumpCompressionBox = new System.Windows.Forms.ComboBox(); + packAllHousingBinLabel = new System.Windows.Forms.Label(); + packAllHousingBin = new System.Windows.Forms.TextBox(); + packAllHousingBinBtn = new System.Windows.Forms.Button(); label13 = new System.Windows.Forms.Label(); inputfolder = new System.Windows.Forms.TextBox(); SelectFolderButton = new System.Windows.Forms.Button(); @@ -447,7 +452,7 @@ private void InitializeComponent() OperationTypeTabControl.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); OperationTypeTabControl.Name = "OperationTypeTabControl"; OperationTypeTabControl.SelectedIndex = 0; - OperationTypeTabControl.Size = new System.Drawing.Size(645, 470); + OperationTypeTabControl.Size = new System.Drawing.Size(640, 422); OperationTypeTabControl.TabIndex = 43; // // ExtractAllFilesTabPage @@ -456,6 +461,11 @@ private void InitializeComponent() ExtractAllFilesTabPage.Controls.Add(ExtractionStatusStrip); ExtractAllFilesTabPage.Controls.Add(pack); ExtractAllFilesTabPage.Controls.Add(extract); + ExtractAllFilesTabPage.Controls.Add(packAllGumpCompressionLabel); + ExtractAllFilesTabPage.Controls.Add(packAllGumpCompressionBox); + ExtractAllFilesTabPage.Controls.Add(packAllHousingBinLabel); + ExtractAllFilesTabPage.Controls.Add(packAllHousingBin); + ExtractAllFilesTabPage.Controls.Add(packAllHousingBinBtn); ExtractAllFilesTabPage.Controls.Add(label13); ExtractAllFilesTabPage.Controls.Add(inputfolder); ExtractAllFilesTabPage.Controls.Add(SelectFolderButton); @@ -463,18 +473,18 @@ private void InitializeComponent() ExtractAllFilesTabPage.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); ExtractAllFilesTabPage.Name = "ExtractAllFilesTabPage"; ExtractAllFilesTabPage.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); - ExtractAllFilesTabPage.Size = new System.Drawing.Size(637, 442); + ExtractAllFilesTabPage.Size = new System.Drawing.Size(632, 394); ExtractAllFilesTabPage.TabIndex = 1; ExtractAllFilesTabPage.Text = "Every file"; ExtractAllFilesTabPage.UseVisualStyleBackColor = true; // // StartFolderButton // - StartFolderButton.Location = new System.Drawing.Point(56, 96); + StartFolderButton.Location = new System.Drawing.Point(56, 154); StartFolderButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); StartFolderButton.Name = "StartFolderButton"; StartFolderButton.Size = new System.Drawing.Size(279, 27); - StartFolderButton.TabIndex = 12; + StartFolderButton.TabIndex = 13; StartFolderButton.Text = "Start"; StartFolderButton.UseVisualStyleBackColor = true; StartFolderButton.Click += StartFolderButtonClick; @@ -482,10 +492,10 @@ private void InitializeComponent() // ExtractionStatusStrip // ExtractionStatusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { statustext, toolStripStatusLabel2 }); - ExtractionStatusStrip.Location = new System.Drawing.Point(4, 417); + ExtractionStatusStrip.Location = new System.Drawing.Point(4, 369); ExtractionStatusStrip.Name = "ExtractionStatusStrip"; ExtractionStatusStrip.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0); - ExtractionStatusStrip.Size = new System.Drawing.Size(629, 22); + ExtractionStatusStrip.Size = new System.Drawing.Size(624, 22); ExtractionStatusStrip.TabIndex = 11; ExtractionStatusStrip.Text = "statusStrip2"; // @@ -493,7 +503,7 @@ private void InitializeComponent() // statustext.ForeColor = System.Drawing.Color.DarkRed; statustext.Name = "statustext"; - statustext.Size = new System.Drawing.Size(330, 17); + statustext.Size = new System.Drawing.Size(325, 17); statustext.Spring = true; statustext.Text = "Status"; statustext.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; @@ -528,6 +538,60 @@ private void InitializeComponent() extract.Text = "Extract UOP to MUL"; extract.UseVisualStyleBackColor = true; // + // packAllGumpCompressionLabel + // + packAllGumpCompressionLabel.AutoSize = true; + packAllGumpCompressionLabel.Location = new System.Drawing.Point(7, 99); + packAllGumpCompressionLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + packAllGumpCompressionLabel.Name = "packAllGumpCompressionLabel"; + packAllGumpCompressionLabel.Size = new System.Drawing.Size(114, 15); + packAllGumpCompressionLabel.TabIndex = 11; + packAllGumpCompressionLabel.Text = "Gump compression:"; + // + // packAllGumpCompressionBox + // + packAllGumpCompressionBox.BackColor = System.Drawing.Color.White; + packAllGumpCompressionBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + packAllGumpCompressionBox.Items.AddRange(new object[] { "None", "Mythic" }); + packAllGumpCompressionBox.Location = new System.Drawing.Point(129, 96); + packAllGumpCompressionBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + packAllGumpCompressionBox.Name = "packAllGumpCompressionBox"; + packAllGumpCompressionBox.Size = new System.Drawing.Size(206, 23); + packAllGumpCompressionBox.TabIndex = 12; + compressionTip.SetToolTip(packAllGumpCompressionBox, "Compression for gumpartLegacyMUL.uop only.\r\nMultiCollection.uop is always Zlib; art/sound/maps are stored uncompressed."); + // + // packAllHousingBinLabel + // + packAllHousingBinLabel.AutoSize = true; + packAllHousingBinLabel.Location = new System.Drawing.Point(8, 128); + packAllHousingBinLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + packAllHousingBinLabel.Name = "packAllHousingBinLabel"; + packAllHousingBinLabel.Size = new System.Drawing.Size(70, 15); + packAllHousingBinLabel.TabIndex = 14; + packAllHousingBinLabel.Text = "housing.bin"; + // + // packAllHousingBin + // + packAllHousingBin.BackColor = System.Drawing.Color.White; + packAllHousingBin.Location = new System.Drawing.Point(86, 125); + packAllHousingBin.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + packAllHousingBin.Name = "packAllHousingBin"; + packAllHousingBin.PlaceholderText = "optional, defaults to folder/housing.bin"; + packAllHousingBin.Size = new System.Drawing.Size(210, 23); + packAllHousingBin.TabIndex = 15; + compressionTip.SetToolTip(packAllHousingBin, "Path to housing.bin used when packing MultiCollection.uop.\r\nLeave empty to use 'housing.bin' inside the folder above."); + // + // packAllHousingBinBtn + // + packAllHousingBinBtn.Location = new System.Drawing.Point(304, 125); + packAllHousingBinBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + packAllHousingBinBtn.Name = "packAllHousingBinBtn"; + packAllHousingBinBtn.Size = new System.Drawing.Size(31, 23); + packAllHousingBinBtn.TabIndex = 16; + packAllHousingBinBtn.Text = "..."; + packAllHousingBinBtn.UseVisualStyleBackColor = true; + packAllHousingBinBtn.Click += PackAllHousingBinSelect; + // // label13 // label13.AutoSize = true; @@ -598,7 +662,7 @@ private void InitializeComponent() ExtractSingleFileTabPage.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); ExtractSingleFileTabPage.Name = "ExtractSingleFileTabPage"; ExtractSingleFileTabPage.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); - ExtractSingleFileTabPage.Size = new System.Drawing.Size(637, 442); + ExtractSingleFileTabPage.Size = new System.Drawing.Size(632, 394); ExtractSingleFileTabPage.TabIndex = 0; ExtractSingleFileTabPage.Text = "One file"; ExtractSingleFileTabPage.UseVisualStyleBackColor = true; @@ -637,11 +701,11 @@ private void InitializeComponent() // MainStatusStrip.Enabled = false; MainStatusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { toolStripStatusLabel1, guilabel, VersionLabel }); - MainStatusStrip.Location = new System.Drawing.Point(0, 8); + MainStatusStrip.Location = new System.Drawing.Point(0, 5); MainStatusStrip.Name = "MainStatusStrip"; MainStatusStrip.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0); MainStatusStrip.RenderMode = System.Windows.Forms.ToolStripRenderMode.Professional; - MainStatusStrip.Size = new System.Drawing.Size(645, 22); + MainStatusStrip.Size = new System.Drawing.Size(640, 22); MainStatusStrip.Stretch = false; MainStatusStrip.TabIndex = 44; MainStatusStrip.Text = "statusStrip1"; @@ -679,8 +743,8 @@ private void InitializeComponent() // splitContainer.Panel2 // splitContainer.Panel2.Controls.Add(MainStatusStrip); - splitContainer.Size = new System.Drawing.Size(645, 505); - splitContainer.SplitterDistance = 470; + splitContainer.Size = new System.Drawing.Size(640, 454); + splitContainer.SplitterDistance = 422; splitContainer.SplitterWidth = 5; splitContainer.TabIndex = 40; // @@ -694,7 +758,7 @@ private void InitializeComponent() DoubleBuffered = true; Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); Name = "UopPackerControl"; - Size = new System.Drawing.Size(645, 505); + Size = new System.Drawing.Size(640, 454); ((System.ComponentModel.ISupportInitialize)mulMapIndex).EndInit(); ((System.ComponentModel.ISupportInitialize)uopMapIndex).EndInit(); OperationTypeTabControl.ResumeLayout(false); @@ -765,6 +829,11 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripStatusLabel VersionLabel; private System.Windows.Forms.ComboBox compressionBox; private System.Windows.Forms.Label compressionLabel; + private System.Windows.Forms.ComboBox packAllGumpCompressionBox; + private System.Windows.Forms.Label packAllGumpCompressionLabel; + private System.Windows.Forms.TextBox packAllHousingBin; + private System.Windows.Forms.Button packAllHousingBinBtn; + private System.Windows.Forms.Label packAllHousingBinLabel; private System.Windows.Forms.ToolTip compressionTip; private System.Windows.Forms.TextBox inhousingbin; private System.Windows.Forms.Button inhousingbinbtn; diff --git a/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.cs b/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.cs index e5a1887..4dd3900 100644 --- a/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.cs +++ b/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.cs @@ -46,12 +46,29 @@ private UopPackerControl() outfolder.PlaceholderText = "folder where .mul/.idx will be written"; outuopfolder.PlaceholderText = "folder where .uop will be written"; + packAllGumpCompressionBox.SelectedIndex = 0; + extract.CheckedChanged += OnPackAllModeChanged; + pack.CheckedChanged += OnPackAllModeChanged; + UpdatePackAllCompressionVisibility(); + RefreshMulTypeUi(); RefreshUopTypeUi(); Dock = DockStyle.Fill; } + private void OnPackAllModeChanged(object sender, EventArgs e) => UpdatePackAllCompressionVisibility(); + + private void UpdatePackAllCompressionVisibility() + { + bool show = pack.Checked; + packAllGumpCompressionLabel.Visible = show; + packAllGumpCompressionBox.Visible = show; + packAllHousingBinLabel.Visible = show; + packAllHousingBin.Visible = show; + packAllHousingBinBtn.Visible = show; + } + private static (string mul, string idx, string uop) GetConventionalNames(FileType type, int mapIndex) { return type switch @@ -384,6 +401,25 @@ private void SelectFolder_Click(object sender, EventArgs e) if (FolderDialog.ShowDialog() == DialogResult.OK) { inputfolder.Text = FolderDialog.SelectedPath; + + if (string.IsNullOrWhiteSpace(packAllHousingBin.Text)) + { + string candidate = Path.Combine(FolderDialog.SelectedPath, "housing.bin"); + if (File.Exists(candidate)) + { + packAllHousingBin.Text = candidate; + } + } + } + } + + private void PackAllHousingBinSelect(object sender, EventArgs e) + { + FileDialog.FilterIndex = 4; + + if (FileDialog.ShowDialog() == DialogResult.OK) + { + packAllHousingBin.Text = FileDialog.FileName; } } @@ -436,7 +472,7 @@ private void Extract(string inFile, string outFile, string outIdx, FileType type } } - private void Pack(string inFile, string inIdx, string outFile, FileType type, int typeIndex, string housingBinFile = "") + private void Pack(string inFile, string inIdx, string outFile, FileType type, int typeIndex, CompressionFlag compression, string housingBinFile = "") { try { @@ -466,9 +502,8 @@ private void Pack(string inFile, string inIdx, string outFile, FileType type, in } ++_total; - CompressionFlag selectedCompressionMethod = Enum.Parse(compressionBox.SelectedItem.ToString()); - LegacyMulFileConverter.ToUop(inFile, inIdx, outFile, type, typeIndex, selectedCompressionMethod, housingBinFile ?? string.Empty); + LegacyMulFileConverter.ToUop(inFile, inIdx, outFile, type, typeIndex, compression, housingBinFile ?? string.Empty); ++_success; } @@ -523,19 +558,41 @@ private void StartFolderButtonClick(object sender, EventArgs e) } else if (pack.Checked) { + CompressionFlag gumpCompression = CompressionFlag.None; + if (packAllGumpCompressionBox.SelectedItem != null) + { + Enum.TryParse(packAllGumpCompressionBox.SelectedItem.ToString(), out gumpCompression); + } + + string housingBinPath = string.IsNullOrWhiteSpace(packAllHousingBin.Text) + ? "housing.bin" + : packAllHousingBin.Text; + + if (!string.IsNullOrWhiteSpace(packAllHousingBin.Text)) + { + string resolved = Path.IsPathRooted(housingBinPath) + ? housingBinPath + : Path.Combine(inputfolder.Text, housingBinPath); + if (!File.Exists(resolved)) + { + MessageBox.Show($"The specified housing.bin does not exist:\r\n{resolved}"); + return; + } + } + _success = _total = 0; - Pack("art.mul", "artidx.mul", "artLegacyMUL.uop", FileType.ArtLegacyMul, 0); - Pack("gumpart.mul", "gumpidx.mul", "gumpartLegacyMUL.uop", FileType.GumpartLegacyMul, 0); - Pack("sound.mul", "soundidx.mul", "soundLegacyMUL.uop", FileType.SoundLegacyMul, 0); - Pack("multi-unpacked.mul", "multi-unpacked.idx", "MultiCollection.uop", FileType.MultiCollection, 0, "housing.bin"); + Pack("art.mul", "artidx.mul", "artLegacyMUL.uop", FileType.ArtLegacyMul, 0, CompressionFlag.None); + Pack("gumpart.mul", "gumpidx.mul", "gumpartLegacyMUL.uop", FileType.GumpartLegacyMul, 0, gumpCompression); + Pack("sound.mul", "soundidx.mul", "soundLegacyMUL.uop", FileType.SoundLegacyMul, 0, CompressionFlag.None); + Pack("multi-unpacked.mul", "multi-unpacked.idx", "MultiCollection.uop", FileType.MultiCollection, 0, CompressionFlag.Zlib, housingBinPath); for (int i = 0; i <= 5; ++i) { string map = $"map{i}"; - Pack(map + ".mul", null, map + "LegacyMUL.uop", FileType.MapLegacyMul, i); - Pack(map + "x.mul", null, map + "xLegacyMUL.uop", FileType.MapLegacyMul, i); + Pack(map + ".mul", null, map + "LegacyMUL.uop", FileType.MapLegacyMul, i, CompressionFlag.None); + Pack(map + "x.mul", null, map + "xLegacyMUL.uop", FileType.MapLegacyMul, i, CompressionFlag.None); } string packMessage = $"Done ({_success}/{_total} files packed)"; diff --git a/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.resx b/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.resx index 02402df..2ca8b4d 100644 --- a/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.resx +++ b/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.resx @@ -148,6 +148,6 @@ Picking the wrong option produces a UOP that is much larger than the original or 398, 17 - 25 + 72 \ No newline at end of file From 90cbf2313e034f486a233b4f3c11289efc81c1f6 Mon Sep 17 00:00:00 2001 From: AsY!um- <377468+AsYlum-@users.noreply.github.com> Date: Fri, 1 May 2026 12:12:42 +0200 Subject: [PATCH 2/4] Remove unused using. --- UoFiddler.Controls/UserControls/ClilocControl.cs | 1 - UoFiddler.Controls/UserControls/HuesControl.cs | 1 - UoFiddler.Controls/UserControls/LightControl.cs | 1 - UoFiddler.Controls/UserControls/MultisControl.cs | 1 - 4 files changed, 4 deletions(-) diff --git a/UoFiddler.Controls/UserControls/ClilocControl.cs b/UoFiddler.Controls/UserControls/ClilocControl.cs index 7effd58..432a6a2 100644 --- a/UoFiddler.Controls/UserControls/ClilocControl.cs +++ b/UoFiddler.Controls/UserControls/ClilocControl.cs @@ -17,7 +17,6 @@ using UoFiddler.Controls.Classes; using UoFiddler.Controls.Forms; using UoFiddler.Controls.Helpers; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window; namespace UoFiddler.Controls.UserControls { diff --git a/UoFiddler.Controls/UserControls/HuesControl.cs b/UoFiddler.Controls/UserControls/HuesControl.cs index b4e02d4..c416a87 100644 --- a/UoFiddler.Controls/UserControls/HuesControl.cs +++ b/UoFiddler.Controls/UserControls/HuesControl.cs @@ -20,7 +20,6 @@ using UoFiddler.Controls.Classes; using UoFiddler.Controls.Forms; using UoFiddler.Controls.Helpers; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window; namespace UoFiddler.Controls.UserControls { diff --git a/UoFiddler.Controls/UserControls/LightControl.cs b/UoFiddler.Controls/UserControls/LightControl.cs index 5a97506..71fddff 100644 --- a/UoFiddler.Controls/UserControls/LightControl.cs +++ b/UoFiddler.Controls/UserControls/LightControl.cs @@ -18,7 +18,6 @@ using UoFiddler.Controls.Classes; using UoFiddler.Controls.Forms; using UoFiddler.Controls.Helpers; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window; namespace UoFiddler.Controls.UserControls { diff --git a/UoFiddler.Controls/UserControls/MultisControl.cs b/UoFiddler.Controls/UserControls/MultisControl.cs index ac3394a..7460ea5 100644 --- a/UoFiddler.Controls/UserControls/MultisControl.cs +++ b/UoFiddler.Controls/UserControls/MultisControl.cs @@ -20,7 +20,6 @@ using UoFiddler.Controls.Classes; using UoFiddler.Controls.Forms; using UoFiddler.Controls.Helpers; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window; namespace UoFiddler.Controls.UserControls { From 24d986296dc369677198599487a51a7bdb9c879b Mon Sep 17 00:00:00 2001 From: AsY!um- <377468+AsYlum-@users.noreply.github.com> Date: Fri, 1 May 2026 14:07:54 +0200 Subject: [PATCH 3/4] Improve cliloc editing. - Add "Next free" button when adding new cliloc entry. - Add entry will now use current selection when searching for next free number or will seach starting from the number user inputs in the text box. - Add tiledata import preview form. --- .../Classes/TileDataSyncChange.cs | 23 ++ .../Classes/TileDataSyncKind.cs | 20 ++ .../Forms/ClilocAddForm.Designer.cs | 108 ++++---- UoFiddler.Controls/Forms/ClilocAddForm.cs | 21 +- UoFiddler.Controls/Forms/ClilocAddForm.resx | 60 +++++ .../Forms/TileDataSyncPreviewForm.Designer.cs | 190 ++++++++++++++ .../Forms/TileDataSyncPreviewForm.cs | 233 +++++++++++++++++ .../Forms/TileDataSyncPreviewForm.resx | 61 +++++ UoFiddler.Controls/UoFiddler.Controls.csproj | 7 + .../UserControls/ClilocControl.cs | 244 +++++++++++++++--- 10 files changed, 884 insertions(+), 83 deletions(-) create mode 100644 UoFiddler.Controls/Classes/TileDataSyncChange.cs create mode 100644 UoFiddler.Controls/Classes/TileDataSyncKind.cs create mode 100644 UoFiddler.Controls/Forms/TileDataSyncPreviewForm.Designer.cs create mode 100644 UoFiddler.Controls/Forms/TileDataSyncPreviewForm.cs create mode 100644 UoFiddler.Controls/Forms/TileDataSyncPreviewForm.resx diff --git a/UoFiddler.Controls/Classes/TileDataSyncChange.cs b/UoFiddler.Controls/Classes/TileDataSyncChange.cs new file mode 100644 index 0000000..2bd20c6 --- /dev/null +++ b/UoFiddler.Controls/Classes/TileDataSyncChange.cs @@ -0,0 +1,23 @@ +// /*************************************************************************** +// * +// * $Author: Turley +// * +// * "THE BEER-WARE LICENSE" +// * As long as you retain this notice you can do whatever you want with +// * this stuff. If we meet some day, and you think this stuff is worth it, +// * you can buy me a beer in return. +// * +// ***************************************************************************/ + +using UoFiddler.Controls.Forms; + +namespace UoFiddler.Controls.Classes +{ + public class TileDataSyncChange + { + public TileDataSyncKind Kind { get; set; } + public int Number { get; set; } + public string OldText { get; set; } + public string NewText { get; set; } + } +} \ No newline at end of file diff --git a/UoFiddler.Controls/Classes/TileDataSyncKind.cs b/UoFiddler.Controls/Classes/TileDataSyncKind.cs new file mode 100644 index 0000000..ae92452 --- /dev/null +++ b/UoFiddler.Controls/Classes/TileDataSyncKind.cs @@ -0,0 +1,20 @@ +// /*************************************************************************** +// * +// * $Author: Turley +// * +// * "THE BEER-WARE LICENSE" +// * As long as you retain this notice you can do whatever you want with +// * this stuff. If we meet some day, and you think this stuff is worth it, +// * you can buy me a beer in return. +// * +// ***************************************************************************/ + +namespace UoFiddler.Controls.Classes +{ + public enum TileDataSyncKind + { + Add, + Update, + Remove + } +} \ No newline at end of file diff --git a/UoFiddler.Controls/Forms/ClilocAddForm.Designer.cs b/UoFiddler.Controls/Forms/ClilocAddForm.Designer.cs index f6c99c8..0426b27 100644 --- a/UoFiddler.Controls/Forms/ClilocAddForm.Designer.cs +++ b/UoFiddler.Controls/Forms/ClilocAddForm.Designer.cs @@ -39,68 +39,81 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.NumberBox = new System.Windows.Forms.TextBox(); - this.label1 = new System.Windows.Forms.Label(); - this.Add_Button = new System.Windows.Forms.Button(); - this.Cancel_Button = new System.Windows.Forms.Button(); - this.SuspendLayout(); + NumberBox = new System.Windows.Forms.TextBox(); + label1 = new System.Windows.Forms.Label(); + Add_Button = new System.Windows.Forms.Button(); + Cancel_Button = new System.Windows.Forms.Button(); + NextFree_Button = new System.Windows.Forms.Button(); + SuspendLayout(); // // NumberBox // - this.NumberBox.Location = new System.Drawing.Point(46, 14); - this.NumberBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.NumberBox.Name = "NumberBox"; - this.NumberBox.Size = new System.Drawing.Size(153, 23); - this.NumberBox.TabIndex = 0; + NumberBox.Location = new System.Drawing.Point(45, 14); + NumberBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + NumberBox.Name = "NumberBox"; + NumberBox.Size = new System.Drawing.Size(95, 23); + NumberBox.TabIndex = 0; // // label1 // - this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(14, 17); - this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(23, 15); - this.label1.TabIndex = 1; - this.label1.Text = "Nr:"; + label1.AutoSize = true; + label1.Location = new System.Drawing.Point(14, 17); + label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label1.Name = "label1"; + label1.Size = new System.Drawing.Size(23, 15); + label1.TabIndex = 1; + label1.Text = "Nr:"; // // Add_Button // - this.Add_Button.Location = new System.Drawing.Point(18, 44); - this.Add_Button.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.Add_Button.Name = "Add_Button"; - this.Add_Button.Size = new System.Drawing.Size(88, 27); - this.Add_Button.TabIndex = 2; - this.Add_Button.Text = "Add"; - this.Add_Button.UseVisualStyleBackColor = true; - this.Add_Button.Click += new System.EventHandler(this.OnClickAdd); + Add_Button.Location = new System.Drawing.Point(16, 43); + Add_Button.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + Add_Button.Name = "Add_Button"; + Add_Button.Size = new System.Drawing.Size(94, 27); + Add_Button.TabIndex = 2; + Add_Button.Text = "Add"; + Add_Button.UseVisualStyleBackColor = true; + Add_Button.Click += OnClickAdd; // // Cancel_Button // - this.Cancel_Button.Location = new System.Drawing.Point(112, 44); - this.Cancel_Button.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.Cancel_Button.Name = "Cancel_Button"; - this.Cancel_Button.Size = new System.Drawing.Size(88, 27); - this.Cancel_Button.TabIndex = 3; - this.Cancel_Button.Text = "Cancel"; - this.Cancel_Button.UseVisualStyleBackColor = true; - this.Cancel_Button.Click += new System.EventHandler(this.OnClickCancel); + Cancel_Button.Location = new System.Drawing.Point(118, 43); + Cancel_Button.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + Cancel_Button.Name = "Cancel_Button"; + Cancel_Button.Size = new System.Drawing.Size(94, 27); + Cancel_Button.TabIndex = 3; + Cancel_Button.Text = "Cancel"; + Cancel_Button.UseVisualStyleBackColor = true; + Cancel_Button.Click += OnClickCancel; + // + // NextFree_Button + // + NextFree_Button.Location = new System.Drawing.Point(148, 12); + NextFree_Button.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + NextFree_Button.Name = "NextFree_Button"; + NextFree_Button.Size = new System.Drawing.Size(64, 25); + NextFree_Button.TabIndex = 1; + NextFree_Button.Text = "Next free"; + NextFree_Button.UseVisualStyleBackColor = true; + NextFree_Button.Click += OnClickNextFree; // // ClilocAddForm // - this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(214, 81); - this.Controls.Add(this.Cancel_Button); - this.Controls.Add(this.Add_Button); - this.Controls.Add(this.label1); - this.Controls.Add(this.NumberBox); - this.DoubleBuffered = true; - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; - this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.Name = "ClilocAddForm"; - this.Text = "Add CliLoc"; - this.ResumeLayout(false); - this.PerformLayout(); + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + ClientSize = new System.Drawing.Size(224, 81); + Controls.Add(Cancel_Button); + Controls.Add(Add_Button); + Controls.Add(NextFree_Button); + Controls.Add(label1); + Controls.Add(NumberBox); + DoubleBuffered = true; + FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + Name = "ClilocAddForm"; + Text = "Add CliLoc"; + ResumeLayout(false); + PerformLayout(); } @@ -108,6 +121,7 @@ private void InitializeComponent() private System.Windows.Forms.Button Add_Button; private System.Windows.Forms.Button Cancel_Button; + private System.Windows.Forms.Button NextFree_Button; private System.Windows.Forms.Label label1; private System.Windows.Forms.TextBox NumberBox; } diff --git a/UoFiddler.Controls/Forms/ClilocAddForm.cs b/UoFiddler.Controls/Forms/ClilocAddForm.cs index f8f4a60..cbc6f1c 100644 --- a/UoFiddler.Controls/Forms/ClilocAddForm.cs +++ b/UoFiddler.Controls/Forms/ClilocAddForm.cs @@ -19,8 +19,9 @@ public partial class ClilocAddForm : Form { private readonly Func _isNumberFreeAction; private readonly Action _addEntryAction; + private readonly Func _getNextFreeAction; - public ClilocAddForm(Func isNumberFreeAction, Action addEntryAction) + public ClilocAddForm(Func isNumberFreeAction, Action addEntryAction, Func getNextFreeAction, int? initialNumber = null) { InitializeComponent(); @@ -29,6 +30,13 @@ public ClilocAddForm(Func isNumberFreeAction, Action addEntryAct _isNumberFreeAction = isNumberFreeAction; _addEntryAction = addEntryAction; + _getNextFreeAction = getNextFreeAction; + + if (initialNumber.HasValue) + { + NumberBox.Text = initialNumber.Value.ToString(); + NumberBox.SelectAll(); + } } private void OnClickAdd(object sender, EventArgs e) @@ -54,6 +62,17 @@ private void OnClickAdd(object sender, EventArgs e) } } + private void OnClickNextFree(object sender, EventArgs e) + { + int? start = null; + if (int.TryParse(NumberBox.Text, System.Globalization.NumberStyles.Integer, null, out int parsed)) + { + start = parsed; + } + + NumberBox.Text = _getNextFreeAction(start).ToString(); + } + private void OnClickCancel(object sender, EventArgs e) { Close(); diff --git a/UoFiddler.Controls/Forms/ClilocAddForm.resx b/UoFiddler.Controls/Forms/ClilocAddForm.resx index 6dae11d..4edf53f 100644 --- a/UoFiddler.Controls/Forms/ClilocAddForm.resx +++ b/UoFiddler.Controls/Forms/ClilocAddForm.resx @@ -1,4 +1,64 @@ + + diff --git a/UoFiddler.Controls/Forms/TileDataSyncPreviewForm.Designer.cs b/UoFiddler.Controls/Forms/TileDataSyncPreviewForm.Designer.cs new file mode 100644 index 0000000..6e9e8b0 --- /dev/null +++ b/UoFiddler.Controls/Forms/TileDataSyncPreviewForm.Designer.cs @@ -0,0 +1,190 @@ +/*************************************************************************** + * + * $Author: Turley + * + * "THE BEER-WARE LICENSE" + * As long as you retain this notice you can do whatever you want with + * this stuff. If we meet some day, and you think this stuff is worth it, + * you can buy me a beer in return. + * + ***************************************************************************/ + +namespace UoFiddler.Controls.Forms +{ + partial class TileDataSyncPreviewForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + summaryLabel = new System.Windows.Forms.Label(); + changesListView = new System.Windows.Forms.ListView(); + actionColumn = new System.Windows.Forms.ColumnHeader(); + numberColumn = new System.Windows.Forms.ColumnHeader(); + oldTextColumn = new System.Windows.Forms.ColumnHeader(); + newTextColumn = new System.Windows.Forms.ColumnHeader(); + checkAllButton = new System.Windows.Forms.Button(); + uncheckAllButton = new System.Windows.Forms.Button(); + uncheckRemovesButton = new System.Windows.Forms.Button(); + applyButton = new System.Windows.Forms.Button(); + cancelButton = new System.Windows.Forms.Button(); + SuspendLayout(); + // + // summaryLabel + // + summaryLabel.AutoSize = true; + summaryLabel.Location = new System.Drawing.Point(12, 12); + summaryLabel.Name = "summaryLabel"; + summaryLabel.Size = new System.Drawing.Size(0, 15); + summaryLabel.TabIndex = 0; + // + // changesListView + // + changesListView.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + changesListView.CheckBoxes = true; + changesListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { actionColumn, numberColumn, oldTextColumn, newTextColumn }); + changesListView.FullRowSelect = true; + changesListView.GridLines = true; + changesListView.Location = new System.Drawing.Point(12, 38); + changesListView.Name = "changesListView"; + changesListView.Size = new System.Drawing.Size(660, 380); + changesListView.TabIndex = 1; + changesListView.UseCompatibleStateImageBehavior = false; + changesListView.View = System.Windows.Forms.View.Details; + changesListView.VirtualMode = false; + // + // actionColumn + // + actionColumn.Text = "Action"; + actionColumn.Width = 80; + // + // numberColumn + // + numberColumn.Text = "Number"; + numberColumn.Width = 90; + // + // oldTextColumn + // + oldTextColumn.Text = "Current text"; + oldTextColumn.Width = 230; + // + // newTextColumn + // + newTextColumn.Text = "New text"; + newTextColumn.Width = 230; + // + // checkAllButton + // + checkAllButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + checkAllButton.Location = new System.Drawing.Point(12, 428); + checkAllButton.Name = "checkAllButton"; + checkAllButton.Size = new System.Drawing.Size(80, 27); + checkAllButton.TabIndex = 2; + checkAllButton.Text = "Check all"; + checkAllButton.UseVisualStyleBackColor = true; + checkAllButton.Click += OnCheckAll; + // + // uncheckAllButton + // + uncheckAllButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + uncheckAllButton.Location = new System.Drawing.Point(96, 428); + uncheckAllButton.Name = "uncheckAllButton"; + uncheckAllButton.Size = new System.Drawing.Size(80, 27); + uncheckAllButton.TabIndex = 3; + uncheckAllButton.Text = "Uncheck all"; + uncheckAllButton.UseVisualStyleBackColor = true; + uncheckAllButton.Click += OnUncheckAll; + // + // uncheckRemovesButton + // + uncheckRemovesButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + uncheckRemovesButton.Location = new System.Drawing.Point(180, 428); + uncheckRemovesButton.Name = "uncheckRemovesButton"; + uncheckRemovesButton.Size = new System.Drawing.Size(125, 27); + uncheckRemovesButton.TabIndex = 4; + uncheckRemovesButton.Text = "Uncheck removes"; + uncheckRemovesButton.UseVisualStyleBackColor = true; + uncheckRemovesButton.Click += OnUncheckRemoves; + // + // applyButton + // + applyButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + applyButton.DialogResult = System.Windows.Forms.DialogResult.OK; + applyButton.Location = new System.Drawing.Point(497, 428); + applyButton.Name = "applyButton"; + applyButton.Size = new System.Drawing.Size(85, 27); + applyButton.TabIndex = 5; + applyButton.Text = "Apply"; + applyButton.UseVisualStyleBackColor = true; + // + // cancelButton + // + cancelButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + cancelButton.Location = new System.Drawing.Point(587, 428); + cancelButton.Name = "cancelButton"; + cancelButton.Size = new System.Drawing.Size(85, 27); + cancelButton.TabIndex = 6; + cancelButton.Text = "Cancel"; + cancelButton.UseVisualStyleBackColor = true; + // + // TileDataSyncPreviewForm + // + AcceptButton = applyButton; + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + CancelButton = cancelButton; + ClientSize = new System.Drawing.Size(684, 467); + Controls.Add(cancelButton); + Controls.Add(applyButton); + Controls.Add(uncheckRemovesButton); + Controls.Add(uncheckAllButton); + Controls.Add(checkAllButton); + Controls.Add(changesListView); + Controls.Add(summaryLabel); + DoubleBuffered = true; + MinimumSize = new System.Drawing.Size(500, 300); + Name = "TileDataSyncPreviewForm"; + StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + Text = "Sync from TileData — Preview"; + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private System.Windows.Forms.Label summaryLabel; + private System.Windows.Forms.ListView changesListView; + private System.Windows.Forms.ColumnHeader actionColumn; + private System.Windows.Forms.ColumnHeader numberColumn; + private System.Windows.Forms.ColumnHeader oldTextColumn; + private System.Windows.Forms.ColumnHeader newTextColumn; + private System.Windows.Forms.Button checkAllButton; + private System.Windows.Forms.Button uncheckAllButton; + private System.Windows.Forms.Button uncheckRemovesButton; + private System.Windows.Forms.Button applyButton; + private System.Windows.Forms.Button cancelButton; + } +} diff --git a/UoFiddler.Controls/Forms/TileDataSyncPreviewForm.cs b/UoFiddler.Controls/Forms/TileDataSyncPreviewForm.cs new file mode 100644 index 0000000..297d469 --- /dev/null +++ b/UoFiddler.Controls/Forms/TileDataSyncPreviewForm.cs @@ -0,0 +1,233 @@ +/*************************************************************************** + * + * $Author: Turley + * + * "THE BEER-WARE LICENSE" + * As long as you retain this notice you can do whatever you want with + * this stuff. If we meet some day, and you think this stuff is worth it, + * you can buy me a beer in return. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Windows.Forms; +using UoFiddler.Controls.Classes; + +namespace UoFiddler.Controls.Forms +{ + public partial class TileDataSyncPreviewForm : Form + { + private sealed class Row + { + public ListViewItem Item; + public TileDataSyncChange Change; + public bool Checked; + } + + private readonly List _rows = new(); + private readonly Dictionary _rowByItem = new(); + + public IReadOnlyList AcceptedChanges + { + get + { + var list = new List(_rows.Count); + foreach (var row in _rows) + { + if (row.Checked) + { + list.Add(row.Change); + } + } + + return list; + } + } + + private bool _handlerHooked; + private bool _suppressEvents; + + public TileDataSyncPreviewForm(IReadOnlyList changes) + { + InitializeComponent(); + + Icon = Options.GetFiddlerIcon(); + + var items = new ListViewItem[changes.Count]; + for (int i = 0; i < changes.Count; i++) + { + var change = changes[i]; + var item = new ListViewItem(change.Kind.ToString()) + { + Checked = true, + Tag = change, + }; + item.SubItems.Add(change.Number.ToString()); + item.SubItems.Add(change.OldText ?? string.Empty); + item.SubItems.Add(change.NewText ?? string.Empty); + items[i] = item; + + var row = new Row { Item = item, Change = change, Checked = true }; + _rows.Add(row); + _rowByItem[item] = row; + } + + changesListView.BeginUpdate(); + changesListView.Items.AddRange(items); + changesListView.EndUpdate(); + + UpdateSummary(); + } + + protected override void OnShown(EventArgs e) + { + base.OnShown(e); + + Cursor.Current = Cursors.Default; + + if (_handlerHooked) + { + return; + } + + _handlerHooked = true; + changesListView.ItemChecked += OnItemChecked; + } + + private void OnItemChecked(object sender, ItemCheckedEventArgs e) + { + if (_suppressEvents) + { + return; + } + + if (!_rowByItem.TryGetValue(e.Item, out var row)) + { + return; + } + + try + { + row.Checked = e.Item.Checked; + } + catch + { + // Reading Checked can throw if the item's native state is mid-recreation. + // Fall back to inverting our cached state — the event only fires on actual change. + row.Checked = !row.Checked; + } + + UpdateSummary(); + } + + private void UpdateSummary() + { + if (summaryLabel == null) + { + return; + } + + int totalAdd = 0, totalUpdate = 0, totalRemove = 0; + int selAdd = 0, selUpdate = 0, selRemove = 0; + + foreach (var row in _rows) + { + switch (row.Change.Kind) + { + case TileDataSyncKind.Add: + totalAdd++; + + if (row.Checked) + { + selAdd++; + } + + break; + case TileDataSyncKind.Update: + totalUpdate++; + + if (row.Checked) + { + selUpdate++; + } + + break; + case TileDataSyncKind.Remove: + totalRemove++; + + if (row.Checked) + { + selRemove++; + } + + break; + } + } + + summaryLabel.Text = $"Will apply: Add {selAdd}/{totalAdd} Update {selUpdate}/{totalUpdate} Remove {selRemove}/{totalRemove}"; + } + + private void SetCheckedForAll(bool value) + { + Cursor.Current = Cursors.WaitCursor; + _suppressEvents = true; + changesListView.BeginUpdate(); + try + { + foreach (var row in _rows) + { + row.Item.Checked = value; + row.Checked = value; + } + } + finally + { + changesListView.EndUpdate(); + _suppressEvents = false; + } + UpdateSummary(); + Cursor.Current = Cursors.Default; + } + + private void SetCheckedForKind(TileDataSyncKind kind, bool value) + { + Cursor.Current = Cursors.WaitCursor; + _suppressEvents = true; + changesListView.BeginUpdate(); + try + { + foreach (var row in _rows) + { + if (row.Change.Kind == kind) + { + row.Item.Checked = value; + row.Checked = value; + } + } + } + finally + { + changesListView.EndUpdate(); + _suppressEvents = false; + } + UpdateSummary(); + Cursor.Current = Cursors.Default; + } + + private void OnCheckAll(object sender, EventArgs e) + { + SetCheckedForAll(true); + } + + private void OnUncheckAll(object sender, EventArgs e) + { + SetCheckedForAll(false); + } + + private void OnUncheckRemoves(object sender, EventArgs e) + { + SetCheckedForKind(TileDataSyncKind.Remove, false); + } + } +} diff --git a/UoFiddler.Controls/Forms/TileDataSyncPreviewForm.resx b/UoFiddler.Controls/Forms/TileDataSyncPreviewForm.resx new file mode 100644 index 0000000..459ff73 --- /dev/null +++ b/UoFiddler.Controls/Forms/TileDataSyncPreviewForm.resx @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + diff --git a/UoFiddler.Controls/UoFiddler.Controls.csproj b/UoFiddler.Controls/UoFiddler.Controls.csproj index 26402b7..37fb0b0 100644 --- a/UoFiddler.Controls/UoFiddler.Controls.csproj +++ b/UoFiddler.Controls/UoFiddler.Controls.csproj @@ -283,6 +283,10 @@ TileDataControl.cs + + + TileDataSyncPreviewForm.cs + @@ -418,6 +422,9 @@ CollapsibleSplitter.cs + + TileDataSyncPreviewForm.cs + diff --git a/UoFiddler.Controls/UserControls/ClilocControl.cs b/UoFiddler.Controls/UserControls/ClilocControl.cs index 432a6a2..7935303 100644 --- a/UoFiddler.Controls/UserControls/ClilocControl.cs +++ b/UoFiddler.Controls/UserControls/ClilocControl.cs @@ -10,8 +10,10 @@ ***************************************************************************/ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Windows.Forms; using Ultima; using UoFiddler.Controls.Classes; @@ -359,7 +361,19 @@ private void OnCell_dbClick(object sender, DataGridViewCellEventArgs e) private void OnClick_AddEntry(object sender, EventArgs e) { - new ClilocAddForm(IsNumberFree, AddEntry).Show(); + int? initial = null; + if (dataGridView1.SelectedCells.Count > 0) + { + var cellValue = dataGridView1.SelectedCells[0].OwningRow.Cells[0].Value; + if (cellValue is int n) + { + initial = GetNextFreeNumber(n); + } + } + + initial ??= GetNextFreeNumber(null); + + new ClilocAddForm(IsNumberFree, AddEntry, GetNextFreeNumber, initial).Show(); } private void OnClick_DeleteEntry(object sender, EventArgs e) @@ -458,6 +472,28 @@ public bool IsNumberFree(int number) return true; } + public int GetNextFreeNumber(int? startFrom) + { + var clilocIds = new System.Collections.Generic.List(_cliloc.Entries.Count); + clilocIds.AddRange(_cliloc.Entries.Select(entry => entry.Number)); + clilocIds.Sort(); + + int candidate = startFrom ?? (clilocIds.Count > 0 ? clilocIds[0] : 0); + foreach (var id in clilocIds.Where(n => n >= candidate)) + { + if (id == candidate) + { + candidate++; + } + else + { + return candidate; + } + } + + return candidate; + } + public void AddEntry(int number) { int index = 0; @@ -479,6 +515,20 @@ public void AddEntry(int number) ++index; } + + _cliloc.Entries.Add(new StringEntry(number, "", StringEntry.CliLocFlag.Custom)); + + _source.ResetBindings(false); + dataGridView1.Invalidate(); + + int newIndex = _cliloc.Entries.Count - 1; + if (newIndex >= 0 && newIndex < dataGridView1.Rows.Count) + { + dataGridView1.Rows[newIndex].Selected = true; + dataGridView1.FirstDisplayedScrollingRowIndex = newIndex; + } + + Options.ChangedUltimaClass["CliLoc"] = true; } private static void FindEntry_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) @@ -545,6 +595,7 @@ private void OnClickImportCSV(object sender, EventArgs e) string text = split[1].Trim(); int index = 0; + bool handled = false; foreach (StringEntry entry in _cliloc.Entries) { if (entry.Number == id) @@ -555,6 +606,7 @@ private void OnClickImportCSV(object sender, EventArgs e) entry.Flag = StringEntry.CliLocFlag.Modified; count++; } + handled = true; break; } @@ -562,11 +614,18 @@ private void OnClickImportCSV(object sender, EventArgs e) { _cliloc.Entries.Insert(index, new StringEntry(id, text, StringEntry.CliLocFlag.Custom)); count++; + handled = true; break; } ++index; } + if (!handled) + { + _cliloc.Entries.Add(new StringEntry(id, text, StringEntry.CliLocFlag.Custom)); + count++; + } + dataGridView1.Invalidate(); } catch @@ -578,6 +637,8 @@ private void OnClickImportCSV(object sender, EventArgs e) if (count > 0) { Options.ChangedUltimaClass["CliLoc"] = true; + _source.ResetBindings(false); + dataGridView1.Invalidate(); MessageBox.Show(this, $"{count} entries changed.", "Import Done", MessageBoxButtons.OK, MessageBoxIcon.Information); } else @@ -591,60 +652,173 @@ private void OnClickImportCSV(object sender, EventArgs e) private void TileDataToolStripMenuItem_Click(object sender, EventArgs e) { - int count = 0; + Cursor.Current = Cursors.WaitCursor; + + List changes; + TileDataSyncPreviewForm preview; + try + { + changes = BuildTileDataSyncPlan(); + + if (changes.Count == 0) + { + Cursor.Current = Cursors.Default; + MessageBox.Show(this, "No differences between TileData and CliLoc — nothing to sync.", "Sync from TileData", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + preview = new TileDataSyncPreviewForm(changes); + } + catch + { + Cursor.Current = Cursors.Default; + throw; + } + + // Cursor stays as WaitCursor across ShowDialog; the form resets it in OnShown. + using (preview) + { + if (preview.ShowDialog(this) != DialogResult.OK) + { + return; + } + + var accepted = preview.AcceptedChanges; + if (accepted.Count == 0) + { + MessageBox.Show(this, "No changes were selected — nothing applied.", "Sync from TileData", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + Cursor.Current = Cursors.WaitCursor; + int added, updated, removed; + try + { + ApplyTileDataSyncPlan(accepted, out added, out updated, out removed); + } + finally + { + Cursor.Current = Cursors.Default; + } + + if (added + updated + removed > 0) + { + Options.ChangedUltimaClass["CliLoc"] = true; + } + + _source.ResetBindings(false); + dataGridView1.Invalidate(); + + MessageBox.Show( + this, + $"Sync from TileData applied:\r\n\r\nAdded: {added}\r\nUpdated: {updated}\r\nRemoved: {removed}", + "Sync from TileData", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + } + } + + private static List BuildTileDataSyncPlan() + { + var byId = new System.Collections.Generic.Dictionary(_cliloc.Entries.Count); + foreach (StringEntry entry in _cliloc.Entries) + { + byId[entry.Number] = entry; + } + + var changes = new List(); for (int index = 0; index < TileData.ItemTable.Length; index++) { ItemData itemData = TileData.ItemTable[index]; - int baseClilocId = GetCliLocBaseId(index); - int id = index + baseClilocId; + int id = index + GetCliLocBaseId(index); + bool exists = byId.TryGetValue(id, out StringEntry existing); if (string.IsNullOrWhiteSpace(itemData.Name)) { - int i = _cliloc.Entries.FindIndex(x => x.Number == id); - - if (i >= 0) + if (exists) { - _cliloc.Entries.RemoveAt(i); - count++; + changes.Add(new TileDataSyncChange + { + Kind = TileDataSyncKind.Remove, + Number = id, + OldText = existing.Text, + NewText = string.Empty, + }); } } - else + else if (!exists) { - int entryIndex = 0; - foreach (StringEntry entry in _cliloc.Entries) + changes.Add(new TileDataSyncChange { - if (entry.Number == id) - { - if (entry.Text != itemData.Name) - { - entry.Text = itemData.Name; - entry.Flag = StringEntry.CliLocFlag.Modified; - count++; - } + Kind = TileDataSyncKind.Add, + Number = id, + OldText = string.Empty, + NewText = itemData.Name, + }); + } + else if (existing.Text != itemData.Name) + { + changes.Add(new TileDataSyncChange + { + Kind = TileDataSyncKind.Update, + Number = id, + OldText = existing.Text, + NewText = itemData.Name, + }); + } + } - break; - } + return changes; + } + + private static void ApplyTileDataSyncPlan(IReadOnlyList changes, out int added, out int updated, out int removed) + { + added = updated = removed = 0; + + var byId = new System.Collections.Generic.Dictionary(_cliloc.Entries.Count); + foreach (StringEntry entry in _cliloc.Entries) + { + byId[entry.Number] = entry; + } + + bool insertedAny = false; - if (entry.Number > id) + foreach (var change in changes) + { + switch (change.Kind) + { + case TileDataSyncKind.Add: + var fresh = new StringEntry(change.Number, change.NewText, StringEntry.CliLocFlag.Modified); + _cliloc.Entries.Add(fresh); + byId[change.Number] = fresh; + insertedAny = true; + added++; + break; + + case TileDataSyncKind.Update: + if (byId.TryGetValue(change.Number, out StringEntry toUpdate)) { - _cliloc.Entries.Insert(entryIndex, new StringEntry(id, itemData.Name, StringEntry.CliLocFlag.Modified)); - count++; - break; + toUpdate.Text = change.NewText; + toUpdate.Flag = StringEntry.CliLocFlag.Modified; + updated++; } + break; - entryIndex++; - } + case TileDataSyncKind.Remove: + int idx = _cliloc.Entries.FindIndex(x => x.Number == change.Number); + if (idx >= 0) + { + _cliloc.Entries.RemoveAt(idx); + byId.Remove(change.Number); + removed++; + } + break; } } - if (count > 0) - { - Options.ChangedUltimaClass["CliLoc"] = true; - MessageBox.Show(this, $"{count} entries changed.", "Import Done", MessageBoxButtons.OK, MessageBoxIcon.Information); - } - else + if (insertedAny) { - MessageBox.Show(this, "No entries changed.", "Import Done", MessageBoxButtons.OK, MessageBoxIcon.Information); + _cliloc.Entries.Sort(new StringList.NumberComparer(false)); } } From c82723cf6cc1a0d1afb0bb2260dcb8f230380bb0 Mon Sep 17 00:00:00 2001 From: AsY!um- <377468+AsYlum-@users.noreply.github.com> Date: Fri, 1 May 2026 14:11:42 +0200 Subject: [PATCH 4/4] Update change log and version. --- UoFiddler/Forms/AboutBoxForm.resx | 9 ++++++++- UoFiddler/UoFiddler.csproj | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/UoFiddler/Forms/AboutBoxForm.resx b/UoFiddler/Forms/AboutBoxForm.resx index 46d41fc..51abb55 100644 --- a/UoFiddler/Forms/AboutBoxForm.resx +++ b/UoFiddler/Forms/AboutBoxForm.resx @@ -118,7 +118,14 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Version 4.19.2 + Version 4.19.3 +- Improve cliloc editing. + * Add "Next free" button when adding new cliloc entry. + * Add entry will now use current selection when searching for next free number or will seach starting from the number user inputs in the text box. + * Add tiledata import preview form. +- Update extract/pack all logic in UopPacker plugin. + +Version 4.19.2 - Fix validation when adding and replacing images in Items and Land tiles tabs. - Added tooltips explaining compression settings in UopPacker plugin. - Minor fixes to internal logger construction. diff --git a/UoFiddler/UoFiddler.csproj b/UoFiddler/UoFiddler.csproj index 93c29dc..eac88cc 100644 --- a/UoFiddler/UoFiddler.csproj +++ b/UoFiddler/UoFiddler.csproj @@ -9,9 +9,9 @@ UoFiddler UoFiddler Copyright © 2026 - 4.19.2 - 4.19.2 - 4.19.2 + 4.19.3 + 4.19.3 + 4.19.3 true