diff --git a/Ultima/Art.cs b/Ultima/Art.cs
index 4f3b87e4..4ab3b15c 100644
--- a/Ultima/Art.cs
+++ b/Ultima/Art.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
@@ -34,6 +35,32 @@ static Art()
_removed = new bool[0x14000];
}
+ ///
+ /// Validates if a static bitmap will fit within the MUL format limits.
+ /// The format uses 16-bit lookup table offsets, limiting total encoded data to ~65,535 ushorts.
+ ///
+ /// The bitmap to validate
+ /// Estimated size in ushorts (output)
+ /// True if the image should fit, false if it exceeds limits
+ public static unsafe bool ValidateStaticSize(Bitmap bmp, out int estimatedSize)
+ {
+ estimatedSize = 0;
+ if (bmp == null || bmp.Width <= 0 || bmp.Height <= 0)
+ {
+ return true;
+ }
+
+ // Estimate worst case: each scanline has full width of visible pixels
+ // Format: 2 ushorts for offset/run + width ushorts for data + 2 ushorts for end markers per line
+ int maxUshortsPerLine = 4 + bmp.Width;
+ estimatedSize = bmp.Height * maxUshortsPerLine;
+
+ // The lookup table uses 16-bit offsets, so we're limited to 65535 ushorts
+ const int maxUshorts = 65535;
+
+ return estimatedSize <= maxUshorts;
+ }
+
public static int GetMaxItemId()
{
// High Seas
@@ -101,11 +128,20 @@ public static void Reload()
///
///
///
+ /// Thrown when the bitmap is too large for the MUL format
public static void ReplaceStatic(int index, Bitmap bmp)
{
index = GetLegalItemId(index);
index += 0x4000;
+ if (bmp != null && !ValidateStaticSize(bmp, out int estimatedSize))
+ {
+ throw new ArgumentException(
+ $"Image is too large for MUL format. Estimated size: {estimatedSize} ushorts (max: 65535). " +
+ $"Image dimensions: {bmp.Width}x{bmp.Height}. " +
+ "Consider using a smaller image or one with more transparent pixels.");
+ }
+
_cache[index] = bmp;
_removed[index] = false;
@@ -651,6 +687,19 @@ public static unsafe void Save(string path)
}
else
{
+ // Validate static art size before encoding
+ if (!ValidateStaticSize(bmp, out int estimatedSize))
+ {
+ // Skip this image and write empty entry
+ binidx.Write(-1); // lookup
+ binidx.Write(0); // Length
+ binidx.Write(-1); // extra
+ System.Diagnostics.Debug.WriteLine(
+ $"Warning: Skipping static art at index {index - 0x4000} - " +
+ $"image too large ({bmp.Width}x{bmp.Height}, estimated {estimatedSize} ushorts, max 65535)");
+ continue;
+ }
+
byte[] imageData = bmp.ToArray(PixelFormat.Format16bppArgb1555).ToSha256();
if (CompareSaveImagesStatic(imageData, out ImageData resultImageData))
{
@@ -697,7 +746,7 @@ public static unsafe void Save(string path)
continue;
}
- if (cur[i] != 0)
+ if ((cur[i] & 0x8000) != 0)
{
break;
}
@@ -712,7 +761,7 @@ public static unsafe void Save(string path)
for (j = i + 1; j < bmp.Width; ++j)
{
// next non set pixel
- if (cur[j] == 0)
+ if ((cur[j] & 0x8000) == 0)
{
break;
}
diff --git a/Ultima/Map.cs b/Ultima/Map.cs
index e06bd702..d170dbdb 100644
--- a/Ultima/Map.cs
+++ b/Ultima/Map.cs
@@ -1,3 +1,4 @@
+using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
@@ -5,6 +6,101 @@
namespace Ultima
{
+ ///
+ /// Altitude rendering mode for map preview generation
+ ///
+ public enum MapAltitudeMode
+ {
+ ///
+ /// Normal flat rendering without altitude effects
+ ///
+ Normal,
+ ///
+ /// Normal rendering with altitude-based shading
+ ///
+ NormalWithAltitude,
+ ///
+ /// Pure altitude map (grayscale based on height)
+ ///
+ Altitude
+ }
+
+ ///
+ /// Altitude shading preset configuration
+ ///
+ public enum AltitudeShadingPreset
+ {
+ ///
+ /// Dramatic, high-contrast shading with sharp edges
+ ///
+ Sharp,
+ ///
+ /// More pronounced shading with higher contrast
+ ///
+ Normal,
+ ///
+ /// Very subtle, smooth shading (matches UO client closely)
+ ///
+ Soft,
+ ///
+ /// Custom settings (uses manual configuration)
+ ///
+ Custom
+ }
+
+ ///
+ /// Configuration for altitude-based shading effects
+ ///
+ public class AltitudeShadingSettings
+ {
+ ///
+ /// Surface normal Z-component (higher = softer shading)
+ /// Sharp: 2.0, Normal: 4.0, Soft: 8.0+
+ ///
+ public float NormalZ { get; set; } = 8.0f;
+
+ ///
+ /// Brightness variation range (0.0 to 0.5)
+ /// Sharp: 0.40 (±40%), Normal: 0.30 (±30%), Soft: 0.15 (±15%)
+ ///
+ public float BrightnessRange { get; set; } = 0.15f;
+
+ ///
+ /// Altitude gradient smoothing factor
+ /// Sharp: 0.75, Normal: 0.50, Soft: 0.25
+ ///
+ public float GradientSmoothing { get; set; } = 0.25f;
+
+ ///
+ /// Gets preset configuration
+ ///
+ public static AltitudeShadingSettings GetPreset(AltitudeShadingPreset preset)
+ {
+ return preset switch
+ {
+ AltitudeShadingPreset.Sharp => new AltitudeShadingSettings
+ {
+ NormalZ = 2.0f,
+ BrightnessRange = 0.40f,
+ GradientSmoothing = 0.75f
+ },
+ AltitudeShadingPreset.Normal => new AltitudeShadingSettings
+ {
+ NormalZ = 4.0f,
+ BrightnessRange = 0.30f,
+ GradientSmoothing = 0.50f
+ },
+ AltitudeShadingPreset.Soft => new AltitudeShadingSettings
+ {
+ NormalZ = 8.0f,
+ BrightnessRange = 0.15f,
+ GradientSmoothing = 0.25f
+ },
+ _ => new AltitudeShadingSettings() // Default to Soft
+ };
+ }
+ }
+
public sealed class Map
{
private TileMatrix _tiles;
@@ -12,6 +108,22 @@ public sealed class Map
private readonly string _path;
private static bool _useDiff;
+ ///
+ /// Controls the intensity of altitude-based shading (1-20, lower = more contrast)
+ /// Default is 15 for subtle effect
+ ///
+ public static int AltitudeIntensity { get; set; } = 15;
+
+ ///
+ /// Current altitude shading preset
+ ///
+ public static AltitudeShadingPreset ShadingPreset { get; set; } = AltitudeShadingPreset.Normal;
+
+ ///
+ /// Custom altitude shading settings (used when ShadingPreset is Custom)
+ ///
+ public static AltitudeShadingSettings CustomShadingSettings { get; set; } = AltitudeShadingSettings.GetPreset(AltitudeShadingPreset.Soft);
+
public static bool UseDiff
{
get { return _useDiff; }
@@ -947,5 +1059,363 @@ public void ReportInvalidMapIDs(string reportFile)
}
}
}
+
+ #region Altitude Map Rendering
+
+ ///
+ /// Returns Bitmap with altitude rendering mode support
+ ///
+ /// 8x8 Block X
+ /// 8x8 Block Y
+ /// Width in 8x8 Blocks
+ /// Height in 8x8 Blocks
+ /// Include statics in rendering
+ /// Altitude rendering mode
+ /// Rendered bitmap
+ public Bitmap GetImageWithAltitude(int x, int y, int width, int height, bool statics, MapAltitudeMode altitudeMode)
+ {
+ PixelFormat format = altitudeMode == MapAltitudeMode.Altitude
+ ? PixelFormat.Format8bppIndexed
+ : PixelFormat.Format16bppRgb555;
+
+ var bmp = new Bitmap(width << 3, height << 3, format);
+
+ if (altitudeMode == MapAltitudeMode.Altitude)
+ {
+ // Create grayscale palette for altitude map
+ ColorPalette palette = bmp.Palette;
+ for (int i = 0; i < 256; i++)
+ {
+ palette.Entries[i] = Color.FromArgb(i, i, i);
+ }
+ bmp.Palette = palette;
+ }
+
+ GetImageWithAltitude(x, y, width, height, bmp, statics, altitudeMode);
+
+ return bmp;
+ }
+
+ ///
+ /// Draws in given Bitmap with altitude rendering mode support
+ ///
+ /// 8x8 Block X
+ /// 8x8 Block Y
+ /// Width in 8x8 Blocks
+ /// Height in 8x8 Blocks
+ /// Target bitmap
+ /// Include statics in rendering
+ /// Altitude rendering mode
+ public unsafe void GetImageWithAltitude(int x, int y, int width, int height, Bitmap bmp, bool statics, MapAltitudeMode altitudeMode)
+ {
+ PixelFormat format = altitudeMode == MapAltitudeMode.Altitude
+ ? PixelFormat.Format8bppIndexed
+ : PixelFormat.Format16bppRgb555;
+
+ BitmapData bd = bmp.LockBits(
+ new Rectangle(0, 0, width << 3, height << 3), ImageLockMode.WriteOnly, format);
+ int stride = bd.Stride;
+ int blockStride = stride << 3;
+
+ var pStart = (byte*)bd.Scan0;
+
+ if (altitudeMode == MapAltitudeMode.Altitude)
+ {
+ // 8-bit altitude mode
+ for (int oy = 0, by = y; oy < height; ++oy, ++by, pStart += blockStride)
+ {
+ var pRow0 = (byte*)(pStart + (0 * stride));
+ var pRow1 = (byte*)(pStart + (1 * stride));
+ var pRow2 = (byte*)(pStart + (2 * stride));
+ var pRow3 = (byte*)(pStart + (3 * stride));
+ var pRow4 = (byte*)(pStart + (4 * stride));
+ var pRow5 = (byte*)(pStart + (5 * stride));
+ var pRow6 = (byte*)(pStart + (6 * stride));
+ var pRow7 = (byte*)(pStart + (7 * stride));
+
+ for (int ox = 0, bx = x; ox < width; ++ox, ++bx)
+ {
+ sbyte[] altitudeData = GetAltitudeBlock(bx, by, statics);
+
+ for (int i = 0; i < 64; i++)
+ {
+ byte altValue = (byte)Math.Clamp(altitudeData[i] + 128, 0, 255);
+ int rowIndex = i / 8;
+ int colIndex = i % 8;
+
+ switch (rowIndex)
+ {
+ case 0: pRow0[ox * 8 + colIndex] = altValue; break;
+ case 1: pRow1[ox * 8 + colIndex] = altValue; break;
+ case 2: pRow2[ox * 8 + colIndex] = altValue; break;
+ case 3: pRow3[ox * 8 + colIndex] = altValue; break;
+ case 4: pRow4[ox * 8 + colIndex] = altValue; break;
+ case 5: pRow5[ox * 8 + colIndex] = altValue; break;
+ case 6: pRow6[ox * 8 + colIndex] = altValue; break;
+ case 7: pRow7[ox * 8 + colIndex] = altValue; break;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // 16-bit color modes (Normal and NormalWithAltitude)
+ for (int oy = 0, by = y; oy < height; ++oy, ++by, pStart += blockStride)
+ {
+ var pRow0 = (ushort*)(pStart + (0 * stride));
+ var pRow1 = (ushort*)(pStart + (1 * stride));
+ var pRow2 = (ushort*)(pStart + (2 * stride));
+ var pRow3 = (ushort*)(pStart + (3 * stride));
+ var pRow4 = (ushort*)(pStart + (4 * stride));
+ var pRow5 = (ushort*)(pStart + (5 * stride));
+ var pRow6 = (ushort*)(pStart + (6 * stride));
+ var pRow7 = (ushort*)(pStart + (7 * stride));
+
+ for (int ox = 0, bx = x; ox < width; ++ox, ++bx)
+ {
+ ushort[] colorData = GetRenderedBlock(bx, by, statics);
+
+ if (altitudeMode == MapAltitudeMode.NormalWithAltitude)
+ {
+ sbyte[] altitudeData = GetAltitudeBlock(bx, by, statics);
+ colorData = ProcessBlockWithAltitude(colorData, altitudeData);
+ }
+
+ for (int i = 0; i < 64; i++)
+ {
+ int rowIndex = i / 8;
+ int colIndex = i % 8;
+
+ switch (rowIndex)
+ {
+ case 0: pRow0[ox * 8 + colIndex] = colorData[i]; break;
+ case 1: pRow1[ox * 8 + colIndex] = colorData[i]; break;
+ case 2: pRow2[ox * 8 + colIndex] = colorData[i]; break;
+ case 3: pRow3[ox * 8 + colIndex] = colorData[i]; break;
+ case 4: pRow4[ox * 8 + colIndex] = colorData[i]; break;
+ case 5: pRow5[ox * 8 + colIndex] = colorData[i]; break;
+ case 6: pRow6[ox * 8 + colIndex] = colorData[i]; break;
+ case 7: pRow7[ox * 8 + colIndex] = colorData[i]; break;
+ }
+ }
+ }
+ }
+ }
+
+ bmp.UnlockBits(bd);
+ _tiles.CloseStreams();
+ }
+
+ ///
+ /// Gets altitude data for an 8x8 block
+ ///
+ private sbyte[] GetAltitudeBlock(int x, int y, bool drawStatics)
+ {
+ var altitudeData = new sbyte[64];
+ TileMatrix matrix = Tiles;
+
+ if (x < 0 || y < 0 || x >= matrix.BlockWidth || y >= matrix.BlockHeight)
+ {
+ return altitudeData;
+ }
+
+ Tile[] tiles = _tiles.GetLandBlock(x, y, UseDiff);
+
+ unsafe
+ {
+ fixed (int* pHeight = TileData.HeightTable)
+ {
+ fixed (Tile* ptTiles = tiles)
+ {
+ Tile* pTiles = ptTiles;
+
+ for (int k = 0; k < 8; ++k)
+ {
+ for (int p = 0; p < 8; ++p)
+ {
+ int idx = k * 8 + p;
+ int highTop = -255;
+ int highZ = -255;
+ int highId = 0;
+
+ if (drawStatics)
+ {
+ HuedTile[][][] statics = _tiles.GetStaticBlock(x, y, UseDiff);
+ HuedTile[] curStatics = statics[p][k];
+
+ if (curStatics.Length > 0)
+ {
+ fixed (HuedTile* phtStatics = curStatics)
+ {
+ HuedTile* pStatics = phtStatics;
+ HuedTile* pStaticsEnd = pStatics + curStatics.Length;
+
+ while (pStatics < pStaticsEnd)
+ {
+ int z = pStatics->Z;
+ int top = z + pHeight[pStatics->Id];
+
+ if (top > highTop || (z > highZ && top >= highTop))
+ {
+ highTop = top;
+ highZ = z;
+ highId = pStatics->Id;
+ }
+
+ ++pStatics;
+ }
+ }
+ }
+
+ StaticTile[] pending = _tiles.GetPendingStatics(x, y);
+ if (pending != null)
+ {
+ foreach (StaticTile penS in pending)
+ {
+ if (penS.X != p || penS.Y != k)
+ {
+ continue;
+ }
+
+ int z = penS.Z;
+ int top = z + pHeight[penS.Id];
+
+ if (top > highTop || (z > highZ && top >= highTop))
+ {
+ highTop = top;
+ highZ = z;
+ highId = penS.Id;
+ }
+ }
+ }
+ }
+
+ int landTop = pTiles->Z;
+ if (landTop > highTop || !drawStatics)
+ {
+ highZ = landTop;
+ }
+
+ altitudeData[idx] = (sbyte)Math.Clamp(highZ, -128, 127);
+ ++pTiles;
+ }
+ }
+ }
+ }
+ }
+
+ return altitudeData;
+ }
+
+ ///
+ /// Process colors with altitude-based shading (translates Pascal ProcessBlock/ProcessQuad/ProcessColor)
+ ///
+ private static ushort[] ProcessBlockWithAltitude(ushort[] colors, sbyte[] altitudes)
+ {
+ // Get current shading settings based on preset
+ AltitudeShadingSettings settings = ShadingPreset == AltitudeShadingPreset.Custom
+ ? CustomShadingSettings
+ : AltitudeShadingSettings.GetPreset(ShadingPreset);
+
+ // Use configurable intensity (lower = more contrast, higher = softer)
+ int maxSlope = Math.Clamp(AltitudeIntensity, 1, 20);
+
+ ushort[] processed = new ushort[64];
+ Array.Copy(colors, processed, 64);
+
+ // Light source comes from northwest (negative X, negative Y direction)
+ const float lightX = -0.577f; // Northwest direction (normalized)
+ const float lightY = -0.577f;
+ const float lightZ = 0.577f; // Equal components for 45-degree angle
+
+ // Calculate lighting for each pixel using its surrounding pixels
+ for (int y = 0; y < 8; y++)
+ {
+ for (int x = 0; x < 8; x++)
+ {
+ int idx = y * 8 + x;
+
+ // Get altitude differences using configurable smoothing
+ float dx = GetAltitudeDifference(altitudes, x, y, 1, 0, settings.GradientSmoothing);
+ float dy = GetAltitudeDifference(altitudes, x, y, 0, 1, settings.GradientSmoothing);
+
+ // Calculate surface normal with configurable nz
+ float nx = -dx;
+ float ny = -dy;
+ float nz = settings.NormalZ + (maxSlope - 10) * 0.5f; // Adjust based on intensity
+
+ // Normalize the normal vector
+ float length = (float)Math.Sqrt(nx * nx + ny * ny + nz * nz);
+ if (length > 0)
+ {
+ nx /= length;
+ ny /= length;
+ nz /= length;
+ }
+
+ // Calculate dot product with light direction (lambertian lighting)
+ float dotProduct = nx * lightX + ny * lightY + nz * lightZ;
+ dotProduct = Math.Clamp(dotProduct, 0.0f, 1.0f);
+
+ // Apply configurable brightness range
+ float baseIntensity = 20.0f / maxSlope; // Higher maxSlope = less intense
+ float range = settings.BrightnessRange * baseIntensity;
+ float multiplier = 1.0f + (dotProduct - 0.5f) * 2.0f * range;
+
+ // Clamp to safe range based on brightness range
+ float minMult = 1.0f - range;
+ float maxMult = 1.0f + range;
+ multiplier = Math.Clamp(multiplier, minMult, maxMult);
+
+ // Apply lighting to the color
+ processed[idx] = ApplyLighting(processed[idx], multiplier);
+ }
+ }
+
+ return processed;
+ }
+
+ ///
+ /// Get altitude difference in a specific direction, with bounds checking and averaging
+ ///
+ private static float GetAltitudeDifference(sbyte[] altitudes, int x, int y, int dx, int dy, float smoothing)
+ {
+ int x1 = Math.Max(0, Math.Min(7, x - dx));
+ int y1 = Math.Max(0, Math.Min(7, y - dy));
+ int x2 = Math.Max(0, Math.Min(7, x + dx));
+ int y2 = Math.Max(0, Math.Min(7, y + dy));
+
+ int idx1 = y1 * 8 + x1;
+ int idx2 = y2 * 8 + x2;
+
+ return (altitudes[idx2] - altitudes[idx1]) * smoothing;
+ }
+
+ ///
+ /// Apply lighting multiplier to a color
+ ///
+ private static ushort ApplyLighting(ushort color, float multiplier)
+ {
+ // Extract RGB components from 15-bit color (RGB555)
+ int red = (color >> 10) & 0x1F;
+ int green = (color >> 5) & 0x1F;
+ int blue = color & 0x1F;
+
+ // Apply multiplier
+ red = (int)(red * multiplier);
+ green = (int)(green * multiplier);
+ blue = (int)(blue * multiplier);
+
+ // Clamp to 5-bit range
+ red = Math.Clamp(red, 0, 31);
+ green = Math.Clamp(green, 0, 31);
+ blue = Math.Clamp(blue, 0, 31);
+
+ // Recombine into RGB555 format
+ return (ushort)((red << 10) | (green << 5) | blue);
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/Ultima/TileData.cs b/Ultima/TileData.cs
index 39c3c1f4..253df27e 100644
--- a/Ultima/TileData.cs
+++ b/Ultima/TileData.cs
@@ -249,6 +249,13 @@ public void ReadData(string[] split)
return;
}
+ // CSV may have been exported from an older client version that did not include extended HSA flags.
+ // Any missing flags default to 0, which is already set above.
+ if (i >= split.Length)
+ {
+ return;
+ }
+
temp = Convert.ToByte(split[i++]);
if (temp != 0)
{
@@ -820,6 +827,13 @@ public void ReadData(string[] split)
return;
}
+ // CSV may have been exported from an older client version that did not include extended HSA flags.
+ // Any missing flags default to 0, which is already set above.
+ if (i >= split.Length)
+ {
+ return;
+ }
+
temp = Convert.ToByte(split[i++]);
if (temp != 0)
{
diff --git a/UoFiddler.Controls/Classes/Options.cs b/UoFiddler.Controls/Classes/Options.cs
index 7d489873..dab5525f 100644
--- a/UoFiddler.Controls/Classes/Options.cs
+++ b/UoFiddler.Controls/Classes/Options.cs
@@ -192,7 +192,8 @@ public static void SetLogger(ILogger logger)
{17, true},
{18, true},
{19, true},
- {20, true}
+ {20, true},
+ {21, true}
};
public static Icon GetFiddlerIcon()
diff --git a/UoFiddler.Controls/Forms/AnimationEditForm.Designer.cs b/UoFiddler.Controls/Forms/AnimationEditForm.Designer.cs
index 6bc5074f..cfdb575c 100644
--- a/UoFiddler.Controls/Forms/AnimationEditForm.Designer.cs
+++ b/UoFiddler.Controls/Forms/AnimationEditForm.Designer.cs
@@ -250,7 +250,7 @@ private void InitializeComponent()
// asBmpToolStripMenuItem
//
asBmpToolStripMenuItem.Name = "asBmpToolStripMenuItem";
- asBmpToolStripMenuItem.Size = new System.Drawing.Size(115, 22);
+ asBmpToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
asBmpToolStripMenuItem.Tag = ".bmp";
asBmpToolStripMenuItem.Text = "As Bmp";
asBmpToolStripMenuItem.Click += OnClickExtractImages;
@@ -258,7 +258,7 @@ private void InitializeComponent()
// asTiffToolStripMenuItem
//
asTiffToolStripMenuItem.Name = "asTiffToolStripMenuItem";
- asTiffToolStripMenuItem.Size = new System.Drawing.Size(115, 22);
+ asTiffToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
asTiffToolStripMenuItem.Tag = ".tiff";
asTiffToolStripMenuItem.Text = "As Tiff";
asTiffToolStripMenuItem.Click += OnClickExtractImages;
@@ -266,7 +266,7 @@ private void InitializeComponent()
// asJpgToolStripMenuItem
//
asJpgToolStripMenuItem.Name = "asJpgToolStripMenuItem";
- asJpgToolStripMenuItem.Size = new System.Drawing.Size(115, 22);
+ asJpgToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
asJpgToolStripMenuItem.Tag = ".jpg";
asJpgToolStripMenuItem.Text = "As Jpg";
asJpgToolStripMenuItem.Click += OnClickExtractImages;
@@ -274,7 +274,7 @@ private void InitializeComponent()
// asPngToolStripMenuItem
//
asPngToolStripMenuItem.Name = "asPngToolStripMenuItem";
- asPngToolStripMenuItem.Size = new System.Drawing.Size(115, 22);
+ asPngToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
asPngToolStripMenuItem.Tag = ".png";
asPngToolStripMenuItem.Text = "As Png";
asPngToolStripMenuItem.Click += OnClickExtractImages;
diff --git a/UoFiddler.Controls/Forms/AnimationEditForm.cs b/UoFiddler.Controls/Forms/AnimationEditForm.cs
index 65e406d8..f2fc4994 100644
--- a/UoFiddler.Controls/Forms/AnimationEditForm.cs
+++ b/UoFiddler.Controls/Forms/AnimationEditForm.cs
@@ -661,8 +661,7 @@ private void OnClickExtractImages(object sender, EventArgs e)
}
}
- MessageBox.Show($"Frames saved to {path}", "Saved", MessageBoxButtons.OK, MessageBoxIcon.Information,
- MessageBoxDefaultButton.Button1);
+ FileSavedDialog.Show(FindForm(), path, "Frames saved successfully.");
}
private void OnClickRemoveAction(object sender, EventArgs e)
@@ -1192,8 +1191,7 @@ private void OnClickExportToVD(object sender, EventArgs e)
string fileName = Path.Combine(path, $"anim{_fileType}_0x{_currentBody:X}.vd");
AnimationEdit.ExportToVD(_fileType, _currentBody, fileName);
- MessageBox.Show($"Animation saved to {Options.OutputPath}", "Export", MessageBoxButtons.OK,
- MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
+ FileSavedDialog.Show(FindForm(), Options.OutputPath, "Animation saved successfully.");
}
private void OnClickShowOnlyValid(object sender, EventArgs e)
diff --git a/UoFiddler.Controls/Forms/AnimationEditForm.resx b/UoFiddler.Controls/Forms/AnimationEditForm.resx
index e474dbfd..ef42f007 100644
--- a/UoFiddler.Controls/Forms/AnimationEditForm.resx
+++ b/UoFiddler.Controls/Forms/AnimationEditForm.resx
@@ -129,12 +129,6 @@
343, 17
-
- 196, 12
-
-
- 448, 47
-
448, 47
@@ -151,6 +145,9 @@
YII=
+
+ 196, 12
+
309, 47
diff --git a/UoFiddler.Controls/Forms/FileSavedDialog.Designer.cs b/UoFiddler.Controls/Forms/FileSavedDialog.Designer.cs
new file mode 100644
index 00000000..0532f9b1
--- /dev/null
+++ b/UoFiddler.Controls/Forms/FileSavedDialog.Designer.cs
@@ -0,0 +1,195 @@
+namespace UoFiddler.Controls.Forms
+{
+ partial class FileSavedDialog
+ {
+ ///
+ /// 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()
+ {
+ mainLayoutPanel = new System.Windows.Forms.TableLayoutPanel();
+ iconPictureBox = new System.Windows.Forms.PictureBox();
+ contentLayoutPanel = new System.Windows.Forms.TableLayoutPanel();
+ statusLabel = new System.Windows.Forms.Label();
+ pathLabel = new System.Windows.Forms.Label();
+ buttonsPanel = new System.Windows.Forms.TableLayoutPanel();
+ buttonOpenFolder = new System.Windows.Forms.Button();
+ buttonOk = new System.Windows.Forms.Button();
+ mainLayoutPanel.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)iconPictureBox).BeginInit();
+ contentLayoutPanel.SuspendLayout();
+ buttonsPanel.SuspendLayout();
+ SuspendLayout();
+ //
+ // mainLayoutPanel
+ //
+ mainLayoutPanel.AutoSize = true;
+ mainLayoutPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+ mainLayoutPanel.ColumnCount = 2;
+ mainLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
+ mainLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
+ mainLayoutPanel.Controls.Add(iconPictureBox, 0, 0);
+ mainLayoutPanel.Controls.Add(contentLayoutPanel, 1, 0);
+ mainLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
+ mainLayoutPanel.Location = new System.Drawing.Point(12, 12);
+ mainLayoutPanel.Margin = new System.Windows.Forms.Padding(0);
+ mainLayoutPanel.Name = "mainLayoutPanel";
+ mainLayoutPanel.RowCount = 1;
+ mainLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
+ mainLayoutPanel.Size = new System.Drawing.Size(560, 86);
+ mainLayoutPanel.TabIndex = 0;
+ //
+ // iconPictureBox
+ //
+ iconPictureBox.Location = new System.Drawing.Point(0, 0);
+ iconPictureBox.Margin = new System.Windows.Forms.Padding(0, 0, 12, 0);
+ iconPictureBox.Name = "iconPictureBox";
+ iconPictureBox.Size = new System.Drawing.Size(32, 32);
+ iconPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage;
+ iconPictureBox.TabIndex = 0;
+ iconPictureBox.TabStop = false;
+ //
+ // contentLayoutPanel
+ //
+ contentLayoutPanel.AutoSize = true;
+ contentLayoutPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+ contentLayoutPanel.ColumnCount = 1;
+ contentLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
+ contentLayoutPanel.Controls.Add(statusLabel, 0, 0);
+ contentLayoutPanel.Controls.Add(pathLabel, 0, 1);
+ contentLayoutPanel.Controls.Add(buttonsPanel, 0, 2);
+ contentLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
+ contentLayoutPanel.Location = new System.Drawing.Point(44, 0);
+ contentLayoutPanel.Margin = new System.Windows.Forms.Padding(0);
+ contentLayoutPanel.Name = "contentLayoutPanel";
+ contentLayoutPanel.RowCount = 3;
+ contentLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
+ contentLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
+ contentLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
+ contentLayoutPanel.Size = new System.Drawing.Size(516, 86);
+ contentLayoutPanel.TabIndex = 1;
+ //
+ // statusLabel
+ //
+ statusLabel.AutoSize = true;
+ statusLabel.Location = new System.Drawing.Point(0, 0);
+ statusLabel.Margin = new System.Windows.Forms.Padding(0, 0, 0, 8);
+ statusLabel.Name = "statusLabel";
+ statusLabel.Size = new System.Drawing.Size(147, 15);
+ statusLabel.TabIndex = 0;
+ statusLabel.Text = "File saved successfully.";
+ //
+ // pathLabel
+ //
+ pathLabel.AutoSize = true;
+ pathLabel.Location = new System.Drawing.Point(0, 23);
+ pathLabel.Margin = new System.Windows.Forms.Padding(0, 0, 0, 12);
+ pathLabel.MaximumSize = new System.Drawing.Size(516, 0);
+ pathLabel.Name = "pathLabel";
+ pathLabel.Size = new System.Drawing.Size(0, 15);
+ pathLabel.TabIndex = 1;
+ pathLabel.UseMnemonic = false;
+ //
+ // buttonsPanel
+ //
+ buttonsPanel.Anchor = System.Windows.Forms.AnchorStyles.Right;
+ buttonsPanel.AutoSize = true;
+ buttonsPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+ buttonsPanel.ColumnCount = 2;
+ buttonsPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
+ buttonsPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
+ buttonsPanel.Controls.Add(buttonOpenFolder, 0, 0);
+ buttonsPanel.Controls.Add(buttonOk, 1, 0);
+ buttonsPanel.Location = new System.Drawing.Point(260, 50);
+ buttonsPanel.Margin = new System.Windows.Forms.Padding(0);
+ buttonsPanel.Name = "buttonsPanel";
+ buttonsPanel.RowCount = 1;
+ buttonsPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
+ buttonsPanel.Size = new System.Drawing.Size(256, 36);
+ buttonsPanel.TabIndex = 2;
+ //
+ // buttonOpenFolder
+ //
+ buttonOpenFolder.AutoSize = false;
+ buttonOpenFolder.Location = new System.Drawing.Point(0, 0);
+ buttonOpenFolder.Margin = new System.Windows.Forms.Padding(0, 0, 8, 0);
+ buttonOpenFolder.Name = "buttonOpenFolder";
+ buttonOpenFolder.Size = new System.Drawing.Size(120, 32);
+ buttonOpenFolder.TabIndex = 0;
+ buttonOpenFolder.Text = "Open Folder";
+ buttonOpenFolder.UseVisualStyleBackColor = true;
+ buttonOpenFolder.Click += OnOpenFolderClick;
+ //
+ // buttonOk
+ //
+ buttonOk.AutoSize = false;
+ buttonOk.DialogResult = System.Windows.Forms.DialogResult.OK;
+ buttonOk.Location = new System.Drawing.Point(128, 0);
+ buttonOk.Margin = new System.Windows.Forms.Padding(0);
+ buttonOk.Name = "buttonOk";
+ buttonOk.Size = new System.Drawing.Size(120, 32);
+ buttonOk.TabIndex = 1;
+ buttonOk.Text = "OK";
+ buttonOk.UseVisualStyleBackColor = true;
+ //
+ // FileSavedDialog
+ //
+ AcceptButton = buttonOk;
+ AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
+ AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+ AutoSize = true;
+ AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+ CancelButton = buttonOk;
+ ClientSize = new System.Drawing.Size(584, 110);
+ Controls.Add(mainLayoutPanel);
+ FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+ MaximizeBox = false;
+ MinimizeBox = false;
+ Name = "FileSavedDialog";
+ Padding = new System.Windows.Forms.Padding(12);
+ ShowInTaskbar = false;
+ StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+ Text = "Saved";
+ mainLayoutPanel.ResumeLayout(false);
+ mainLayoutPanel.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)iconPictureBox).EndInit();
+ contentLayoutPanel.ResumeLayout(false);
+ contentLayoutPanel.PerformLayout();
+ buttonsPanel.ResumeLayout(false);
+ ResumeLayout(false);
+ PerformLayout();
+ }
+
+ #endregion
+
+ private System.Windows.Forms.TableLayoutPanel mainLayoutPanel;
+ private System.Windows.Forms.PictureBox iconPictureBox;
+ private System.Windows.Forms.TableLayoutPanel contentLayoutPanel;
+ private System.Windows.Forms.Label statusLabel;
+ private System.Windows.Forms.Label pathLabel;
+ private System.Windows.Forms.TableLayoutPanel buttonsPanel;
+ private System.Windows.Forms.Button buttonOpenFolder;
+ private System.Windows.Forms.Button buttonOk;
+ }
+}
diff --git a/UoFiddler.Controls/Forms/FileSavedDialog.cs b/UoFiddler.Controls/Forms/FileSavedDialog.cs
new file mode 100644
index 00000000..aebcb92d
--- /dev/null
+++ b/UoFiddler.Controls/Forms/FileSavedDialog.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Diagnostics;
+using System.Drawing;
+using System.IO;
+using System.Windows.Forms;
+
+namespace UoFiddler.Controls.Forms
+{
+ ///
+ /// A reusable dialog that displays a success message when a file is saved,
+ /// with options to close or open the containing folder.
+ ///
+ public sealed partial class FileSavedDialog : Form
+ {
+ private readonly string _filePath;
+
+ public FileSavedDialog(string filePath, string message = null, string title = null)
+ {
+ _filePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
+
+ InitializeComponent();
+
+ // Set the system information icon
+ iconPictureBox.Image = SystemIcons.Information.ToBitmap();
+
+ if (!string.IsNullOrWhiteSpace(title))
+ {
+ Text = title;
+ }
+
+ statusLabel.Text = message ?? "File saved successfully.";
+ pathLabel.Text = _filePath;
+ }
+
+ private void OnOpenFolderClick(object sender, EventArgs e)
+ {
+ try
+ {
+ string folderPath = Directory.Exists(_filePath) ? _filePath : Path.GetDirectoryName(_filePath);
+ if (!string.IsNullOrEmpty(folderPath) && Directory.Exists(folderPath))
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = folderPath,
+ UseShellExecute = true
+ });
+
+ Close();
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(
+ this,
+ $"Unable to open folder: {ex.Message}",
+ "Error",
+ MessageBoxButtons.OK,
+ MessageBoxIcon.Error);
+ }
+ }
+
+ ///
+ /// Shows the file saved dialog.
+ ///
+ /// The owner form.
+ /// The path to the saved file.
+ /// Optional custom success message.
+ /// Optional custom dialog title.
+ public static void Show(IWin32Window owner, string filePath, string message = null, string title = null)
+ {
+ using (var dialog = new FileSavedDialog(filePath, message, title))
+ {
+ dialog.ShowDialog(owner);
+ }
+ }
+
+ ///
+ /// Shows the file saved dialog without an owner.
+ ///
+ /// The path to the saved file.
+ /// Optional custom success message.
+ /// Optional custom dialog title.
+ public static void Show(string filePath, string message = null, string title = null)
+ {
+ using (var dialog = new FileSavedDialog(filePath, message, title))
+ {
+ dialog.ShowDialog();
+ }
+ }
+ }
+}
diff --git a/UoFiddler.Controls/Forms/FileSavedDialog.resx b/UoFiddler.Controls/Forms/FileSavedDialog.resx
new file mode 100644
index 00000000..1af7de15
--- /dev/null
+++ b/UoFiddler.Controls/Forms/FileSavedDialog.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
\ No newline at end of file
diff --git a/UoFiddler.Controls/Forms/ItemDetailForm.cs b/UoFiddler.Controls/Forms/ItemDetailForm.cs
index 4c1276bb..4efed343 100644
--- a/UoFiddler.Controls/Forms/ItemDetailForm.cs
+++ b/UoFiddler.Controls/Forms/ItemDetailForm.cs
@@ -266,8 +266,7 @@ private void SaveImage(ImageFormat imageFormat)
bit.Save(fileName, imageFormat);
}
- MessageBox.Show($"Item saved to {fileName}", "Saved", MessageBoxButtons.OK, MessageBoxIcon.Information,
- MessageBoxDefaultButton.Button1);
+ FileSavedDialog.Show(FindForm(), fileName, "Item image saved successfully.");
}
private void OnSizeChange(object sender, EventArgs e)
diff --git a/UoFiddler.Controls/Forms/MapReplaceForm.cs b/UoFiddler.Controls/Forms/MapReplaceForm.cs
index 7bf6d2e6..98b62b84 100644
--- a/UoFiddler.Controls/Forms/MapReplaceForm.cs
+++ b/UoFiddler.Controls/Forms/MapReplaceForm.cs
@@ -479,7 +479,7 @@ private void OnClickCopy(object sender, EventArgs e)
mStaticsReaderCopy.Close();
}
- MessageBox.Show($"Files saved to {Options.OutputPath}", "Saved", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
+ FileSavedDialog.Show(FindForm(), Options.OutputPath, "Files saved successfully.");
}
private class SupportedMaps
diff --git a/UoFiddler.Controls/Forms/MapReplaceTilesForm.cs b/UoFiddler.Controls/Forms/MapReplaceTilesForm.cs
index e997f838..64d4f58d 100644
--- a/UoFiddler.Controls/Forms/MapReplaceTilesForm.cs
+++ b/UoFiddler.Controls/Forms/MapReplaceTilesForm.cs
@@ -71,8 +71,7 @@ private void OnReplace(object sender, EventArgs e)
richTextBox1.AppendText("Done.");
- MessageBox.Show($"Files saved to {Options.OutputPath}", "Saved", MessageBoxButtons.OK,
- MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
+ FileSavedDialog.Show(FindForm(), Options.OutputPath, "Files saved successfully.");
}
finally
{
diff --git a/UoFiddler.Controls/UserControls/AnimDataControl.Designer.cs b/UoFiddler.Controls/UserControls/AnimDataControl.Designer.cs
index dc362d2e..0efcd3ee 100644
--- a/UoFiddler.Controls/UserControls/AnimDataControl.Designer.cs
+++ b/UoFiddler.Controls/UserControls/AnimDataControl.Designer.cs
@@ -52,6 +52,7 @@ private void InitializeComponent()
toolStripDropDownButton1 = new System.Windows.Forms.ToolStripDropDownButton();
hueToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
animateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ showFrameBoundsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
toolStripStatusBaseGraphic = new System.Windows.Forms.ToolStripStatusLabel();
toolStripStatusGraphic = new System.Windows.Forms.ToolStripStatusLabel();
toolStripStatusHue = new System.Windows.Forms.ToolStripStatusLabel();
@@ -75,7 +76,6 @@ private void InitializeComponent()
numericUpDownFrameDelay = new System.Windows.Forms.NumericUpDown();
label1 = new System.Windows.Forms.Label();
numericUpDownStartDelay = new System.Windows.Forms.NumericUpDown();
- showFrameBoundsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit();
splitContainer1.Panel1.SuspendLayout();
splitContainer1.Panel2.SuspendLayout();
@@ -111,7 +111,7 @@ private void InitializeComponent()
//
splitContainer1.Panel2.Controls.Add(splitContainer2);
splitContainer1.Size = new System.Drawing.Size(857, 587);
- splitContainer1.SplitterDistance = 246;
+ splitContainer1.SplitterDistance = 235;
splitContainer1.SplitterWidth = 5;
splitContainer1.TabIndex = 0;
//
@@ -123,7 +123,7 @@ private void InitializeComponent()
treeView1.Location = new System.Drawing.Point(0, 0);
treeView1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
treeView1.Name = "treeView1";
- treeView1.Size = new System.Drawing.Size(246, 587);
+ treeView1.Size = new System.Drawing.Size(235, 587);
treeView1.TabIndex = 0;
treeView1.AfterSelect += AfterNodeSelect;
treeView1.NodeMouseClick += OnClickNode;
@@ -174,8 +174,8 @@ private void InitializeComponent()
splitContainer2.Panel2.Controls.Add(groupBox3);
splitContainer2.Panel2.Controls.Add(groupBox4);
splitContainer2.Panel2.Controls.Add(groupBox2);
- splitContainer2.Size = new System.Drawing.Size(606, 587);
- splitContainer2.SplitterDistance = 328;
+ splitContainer2.Size = new System.Drawing.Size(617, 587);
+ splitContainer2.SplitterDistance = 284;
splitContainer2.SplitterWidth = 5;
splitContainer2.TabIndex = 6;
//
@@ -188,7 +188,7 @@ private void InitializeComponent()
groupBox1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
groupBox1.Name = "groupBox1";
groupBox1.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
- groupBox1.Size = new System.Drawing.Size(328, 587);
+ groupBox1.Size = new System.Drawing.Size(284, 587);
groupBox1.TabIndex = 1;
groupBox1.TabStop = false;
groupBox1.Text = "Preview";
@@ -198,7 +198,7 @@ private void InitializeComponent()
statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { toolStripDropDownButton1, toolStripStatusBaseGraphic, toolStripStatusGraphic, toolStripStatusHue });
statusStrip1.Location = new System.Drawing.Point(4, 562);
statusStrip1.Name = "statusStrip1";
- statusStrip1.Size = new System.Drawing.Size(320, 22);
+ statusStrip1.Size = new System.Drawing.Size(276, 22);
statusStrip1.TabIndex = 2;
statusStrip1.Text = "statusStrip1";
//
@@ -226,6 +226,13 @@ private void InitializeComponent()
animateToolStripMenuItem.Text = "Animate";
animateToolStripMenuItem.Click += OnClickStartStop;
//
+ // showFrameBoundsToolStripMenuItem
+ //
+ showFrameBoundsToolStripMenuItem.Name = "showFrameBoundsToolStripMenuItem";
+ showFrameBoundsToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
+ showFrameBoundsToolStripMenuItem.Text = "Show frame bounds";
+ showFrameBoundsToolStripMenuItem.Click += OnClickShowFrameBounds;
+ //
// toolStripStatusBaseGraphic
//
toolStripStatusBaseGraphic.Name = "toolStripStatusBaseGraphic";
@@ -235,13 +242,13 @@ private void InitializeComponent()
// toolStripStatusGraphic
//
toolStripStatusGraphic.Name = "toolStripStatusGraphic";
- toolStripStatusGraphic.Size = new System.Drawing.Size(89, 17);
+ toolStripStatusGraphic.Size = new System.Drawing.Size(89, 15);
toolStripStatusGraphic.Text = "Graphic: 0 (0x0)";
//
// toolStripStatusHue
//
toolStripStatusHue.Name = "toolStripStatusHue";
- toolStripStatusHue.Size = new System.Drawing.Size(41, 17);
+ toolStripStatusHue.Size = new System.Drawing.Size(41, 15);
toolStripStatusHue.Text = "Hue: 0";
//
// MainPictureBox
@@ -255,7 +262,7 @@ private void InitializeComponent()
MainPictureBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
MainPictureBox.Name = "MainPictureBox";
MainPictureBox.ShowFrameBounds = false;
- MainPictureBox.Size = new System.Drawing.Size(320, 565);
+ MainPictureBox.Size = new System.Drawing.Size(276, 565);
MainPictureBox.TabIndex = 0;
MainPictureBox.TabStop = false;
//
@@ -282,14 +289,14 @@ private void InitializeComponent()
groupBox3.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
groupBox3.Name = "groupBox3";
groupBox3.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
- groupBox3.Size = new System.Drawing.Size(273, 61);
+ groupBox3.Size = new System.Drawing.Size(328, 61);
groupBox3.TabIndex = 6;
groupBox3.TabStop = false;
//
// button8
//
button8.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
- button8.Location = new System.Drawing.Point(28, 22);
+ button8.Location = new System.Drawing.Point(16, 22);
button8.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
button8.Name = "button8";
button8.Size = new System.Drawing.Size(66, 27);
@@ -301,7 +308,7 @@ private void InitializeComponent()
// button7
//
button7.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
- button7.Location = new System.Drawing.Point(104, 22);
+ button7.Location = new System.Drawing.Point(92, 22);
button7.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
button7.Name = "button7";
button7.Size = new System.Drawing.Size(66, 27);
@@ -313,7 +320,7 @@ private void InitializeComponent()
// button6
//
button6.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
- button6.Location = new System.Drawing.Point(180, 22);
+ button6.Location = new System.Drawing.Point(168, 22);
button6.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
button6.Name = "button6";
button6.Size = new System.Drawing.Size(66, 27);
@@ -336,7 +343,7 @@ private void InitializeComponent()
groupBox4.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
groupBox4.Name = "groupBox4";
groupBox4.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
- groupBox4.Size = new System.Drawing.Size(273, 423);
+ groupBox4.Size = new System.Drawing.Size(328, 423);
groupBox4.TabIndex = 4;
groupBox4.TabStop = false;
groupBox4.Text = "Frames";
@@ -366,10 +373,10 @@ private void InitializeComponent()
// button4
//
button4.Font = new System.Drawing.Font("Microsoft Sans Serif", 7F);
- button4.Location = new System.Drawing.Point(227, 178);
+ button4.Location = new System.Drawing.Point(242, 165);
button4.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
button4.Name = "button4";
- button4.Size = new System.Drawing.Size(26, 25);
+ button4.Size = new System.Drawing.Size(26, 36);
button4.TabIndex = 3;
button4.Text = "▼";
button4.UseVisualStyleBackColor = true;
@@ -390,10 +397,10 @@ private void InitializeComponent()
// button3
//
button3.Font = new System.Drawing.Font("Microsoft Sans Serif", 7F);
- button3.Location = new System.Drawing.Point(227, 147);
+ button3.Location = new System.Drawing.Point(242, 123);
button3.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
button3.Name = "button3";
- button3.Size = new System.Drawing.Size(26, 25);
+ button3.Size = new System.Drawing.Size(26, 36);
button3.TabIndex = 2;
button3.Text = "▲";
button3.UseVisualStyleBackColor = true;
@@ -410,12 +417,11 @@ private void InitializeComponent()
//
// treeViewFrames
//
- treeViewFrames.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
treeViewFrames.HideSelection = false;
treeViewFrames.Location = new System.Drawing.Point(14, 22);
treeViewFrames.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
treeViewFrames.Name = "treeViewFrames";
- treeViewFrames.Size = new System.Drawing.Size(217, 326);
+ treeViewFrames.Size = new System.Drawing.Size(220, 326);
treeViewFrames.TabIndex = 1;
treeViewFrames.AfterSelect += AfterSelectTreeViewFrames;
//
@@ -430,7 +436,7 @@ private void InitializeComponent()
groupBox2.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
groupBox2.Name = "groupBox2";
groupBox2.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
- groupBox2.Size = new System.Drawing.Size(273, 95);
+ groupBox2.Size = new System.Drawing.Size(328, 95);
groupBox2.TabIndex = 2;
groupBox2.TabStop = false;
groupBox2.Text = "Data";
@@ -475,13 +481,6 @@ private void InitializeComponent()
numericUpDownStartDelay.TabIndex = 0;
numericUpDownStartDelay.ValueChanged += OnValueChangedStartDelay;
//
- // showFrameBoundsToolStripMenuItem
- //
- showFrameBoundsToolStripMenuItem.Name = "showFrameBoundsToolStripMenuItem";
- showFrameBoundsToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
- showFrameBoundsToolStripMenuItem.Text = "Show frame bounds";
- showFrameBoundsToolStripMenuItem.Click += OnClickShowFrameBounds;
- //
// AnimDataControl
//
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
@@ -531,7 +530,6 @@ private void InitializeComponent()
private System.Windows.Forms.Label label2;
private System.Windows.Forms.NumericUpDown numericUpDownFrameDelay;
private System.Windows.Forms.NumericUpDown numericUpDownStartDelay;
- private AnimatedPictureBox MainPictureBox;
private System.Windows.Forms.ToolStripMenuItem removeToolStripMenuItem;
private System.Windows.Forms.SplitContainer splitContainer1;
private System.Windows.Forms.SplitContainer splitContainer2;
@@ -555,5 +553,6 @@ private void InitializeComponent()
private System.Windows.Forms.ContextMenuStrip contextMenuStripMainPictureBox;
private System.Windows.Forms.ToolStripMenuItem exportAsAnimatedGifToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem showFrameBoundsToolStripMenuItem;
+ private AnimatedPictureBox MainPictureBox;
}
}
diff --git a/UoFiddler.Controls/UserControls/AnimDataControl.cs b/UoFiddler.Controls/UserControls/AnimDataControl.cs
index 3fa2799d..3ab18b61 100644
--- a/UoFiddler.Controls/UserControls/AnimDataControl.cs
+++ b/UoFiddler.Controls/UserControls/AnimDataControl.cs
@@ -556,12 +556,9 @@ private void OnClickSave(object sender, EventArgs e)
Cursor.Current = Cursors.WaitCursor;
Animdata.Save(Options.OutputPath);
Cursor.Current = Cursors.Default;
- MessageBox.Show($"Saved to {Options.OutputPath}",
- "Save",
- MessageBoxButtons.OK,
- MessageBoxIcon.Information,
- MessageBoxDefaultButton.Button1);
Options.ChangedUltimaClass["Animdata"] = false;
+
+ FileSavedDialog.Show(FindForm(), Options.OutputPath, "File saved successfully.");
}
private void OnClickRemoveAnim(object sender, EventArgs e)
diff --git a/UoFiddler.Controls/UserControls/AnimDataControl.resx b/UoFiddler.Controls/UserControls/AnimDataControl.resx
index 933c20a7..79f3e9e3 100644
--- a/UoFiddler.Controls/UserControls/AnimDataControl.resx
+++ b/UoFiddler.Controls/UserControls/AnimDataControl.resx
@@ -1,7 +1,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+ 17, 17
+
+
\ No newline at end of file
diff --git a/UoFiddler.Plugin.Compare/Classes/SecondAnimdata.cs b/UoFiddler.Plugin.Compare/Classes/SecondAnimdata.cs
new file mode 100644
index 00000000..2facbdc0
--- /dev/null
+++ b/UoFiddler.Plugin.Compare/Classes/SecondAnimdata.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Ultima;
+
+namespace UoFiddler.Plugin.Compare.Classes
+{
+ internal static class SecondAnimdata
+ {
+ private static Dictionary _data;
+
+ public static bool IsLoaded => _data != null;
+
+ public static Animdata.AnimdataEntry GetAnimData(int id) =>
+ _data != null && _data.TryGetValue(id, out Animdata.AnimdataEntry value) ? value : null;
+
+ public static IEnumerable GetKeys() =>
+ _data != null ? (IEnumerable)_data.Keys : Array.Empty();
+
+ ///
+ /// Reads an animdata.mul from the given file path. Returns false if the file
+ /// cannot be read. On success the internal dictionary is replaced.
+ ///
+ public static bool Initialize(string filePath)
+ {
+ if (!File.Exists(filePath))
+ {
+ return false;
+ }
+
+ var result = new Dictionary();
+
+ try
+ {
+ using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
+ using (var bin = new BinaryReader(fs))
+ {
+ unsafe
+ {
+ int id = 0;
+ long chunkCount = bin.BaseStream.Length / (4 + (8 * (64 + 4)));
+
+ for (long h = 0; h < chunkCount; h++)
+ {
+ bin.ReadInt32(); // chunk header (discarded)
+
+ byte[] buffer = bin.ReadBytes(544);
+
+ fixed (byte* buf = buffer)
+ {
+ byte* data = buf;
+
+ for (int i = 0; i < 8; ++i, ++id)
+ {
+ sbyte[] frame = new sbyte[64];
+ for (int j = 0; j < 64; ++j)
+ {
+ frame[j] = (sbyte)*data++;
+ }
+
+ byte unk = *data++;
+ byte frameCount = *data++;
+ byte frameInterval = *data++;
+ byte frameStart = *data++;
+
+ if (frameCount > 0)
+ {
+ result[id] = new Animdata.AnimdataEntry(frame, unk, frameCount, frameInterval, frameStart);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+
+ _data = result;
+ return true;
+ }
+ }
+}
diff --git a/UoFiddler.Plugin.Compare/Classes/SecondArt.cs b/UoFiddler.Plugin.Compare/Classes/SecondArt.cs
index 05e09114..a3d95d27 100644
--- a/UoFiddler.Plugin.Compare/Classes/SecondArt.cs
+++ b/UoFiddler.Plugin.Compare/Classes/SecondArt.cs
@@ -1,4 +1,5 @@
-using System.Drawing;
+using System;
+using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Ultima;
@@ -13,10 +14,13 @@ internal static class SecondArt
private static byte[] _streamBuffer;
private static byte[] _validBuffer;
+ internal static event Action FileIndexChanged;
+
public static void SetFileIndex(string idxPath, string mulPath)
{
_fileIndex = new SecondFileIndex(idxPath, mulPath, 0x14000);
_cache = new Bitmap[0x14000];
+ FileIndexChanged?.Invoke();
}
public static int GetMaxItemId()
@@ -58,6 +62,11 @@ private static int GetIdxLength()
return (int)(_fileIndex.IdxLength / 12);
}
+ public static bool IsUOAHS()
+ {
+ return GetIdxLength() >= 0x13FDC;
+ }
+
public static bool IsValidStatic(int index)
{
index = GetLegalItemId(index);
diff --git a/UoFiddler.Plugin.Compare/Classes/SecondRadarCol.cs b/UoFiddler.Plugin.Compare/Classes/SecondRadarCol.cs
new file mode 100644
index 00000000..bdc27a57
--- /dev/null
+++ b/UoFiddler.Plugin.Compare/Classes/SecondRadarCol.cs
@@ -0,0 +1,49 @@
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace UoFiddler.Plugin.Compare.Classes
+{
+ internal static class SecondRadarCol
+ {
+ private static ushort[] _colors;
+
+ public static bool IsLoaded => _colors != null;
+
+ public static ushort GetColor(int index) =>
+ _colors != null && index >= 0 && index < _colors.Length ? _colors[index] : (ushort)0;
+
+ public static int Length => _colors?.Length ?? 0;
+
+ ///
+ /// Reads a radarcol.mul from the given file path. Returns false if the file
+ /// cannot be read.
+ ///
+ public static bool Initialize(string filePath)
+ {
+ if (!File.Exists(filePath))
+ {
+ return false;
+ }
+
+ try
+ {
+ using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
+ {
+ var colors = new ushort[fs.Length / 2];
+ var buffer = new byte[(int)fs.Length];
+ fs.Read(buffer, 0, (int)fs.Length);
+ GCHandle gc = GCHandle.Alloc(colors, GCHandleType.Pinned);
+ Marshal.Copy(buffer, 0, gc.AddrOfPinnedObject(), (int)fs.Length);
+ gc.Free();
+ _colors = colors;
+ }
+ }
+ catch
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/UoFiddler.Plugin.Compare/Classes/SecondTexture.cs b/UoFiddler.Plugin.Compare/Classes/SecondTexture.cs
index 2e8eb6ad..87f002bf 100644
--- a/UoFiddler.Plugin.Compare/Classes/SecondTexture.cs
+++ b/UoFiddler.Plugin.Compare/Classes/SecondTexture.cs
@@ -24,7 +24,13 @@ public static void SetFileIndex(string idxPath, string mulPath)
public static bool IsValidTexture(int index)
{
+ if (_cache == null)
+ {
+ return false;
+ }
+
index &= 0x3FFF;
+
if (_cache[index] != null)
{
return true;
@@ -37,6 +43,11 @@ public static bool IsValidTexture(int index)
public static Bitmap GetTexture(int index)
{
+ if (_cache == null)
+ {
+ return null;
+ }
+
index &= 0x3FFF;
if (_cache[index] != null)
diff --git a/UoFiddler.Plugin.Compare/Classes/SecondTileData.cs b/UoFiddler.Plugin.Compare/Classes/SecondTileData.cs
new file mode 100644
index 00000000..802a1a1b
--- /dev/null
+++ b/UoFiddler.Plugin.Compare/Classes/SecondTileData.cs
@@ -0,0 +1,114 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using Ultima;
+
+namespace UoFiddler.Plugin.Compare.Classes
+{
+ ///
+ /// Contains lists of land and item tile data.
+ ///
+ ///
+ ///
+ public class SecondTileData
+ {
+ ///
+ /// Gets the list of land tile data.
+ ///
+ public LandData[] LandTable { get; private set; }
+
+ ///
+ /// Gets the list of item tile data.
+ ///
+ public ItemData[] ItemTable { get; private set; }
+
+ public int[] HeightTable { get; private set; }
+
+ public unsafe void Initialize(string path, bool useNeWTileDataFormat)
+ {
+ string filePath = path;
+ if (filePath == null)
+ {
+ return;
+ }
+
+ using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
+ {
+
+ var landHeader = new int[512];
+ int j = 0;
+ LandTable = new LandData[0x4000];
+
+ var buffer = new byte[fs.Length];
+ GCHandle gc = GCHandle.Alloc(buffer, GCHandleType.Pinned);
+ long currentPos = 0;
+ try
+ {
+ fs.Read(buffer, 0, buffer.Length);
+ for (int i = 0; i < 0x4000; i += 32)
+ {
+ var ptrHeader = new IntPtr((long)gc.AddrOfPinnedObject() + currentPos);
+ currentPos += 4;
+ landHeader[j++] = (int)Marshal.PtrToStructure(ptrHeader, typeof(int));
+ for (int count = 0; count < 32; ++count)
+ {
+ var ptr = new IntPtr((long)gc.AddrOfPinnedObject() + currentPos);
+ if (useNeWTileDataFormat)
+ {
+ currentPos += sizeof(NewLandTileDataMul);
+ var cur = (NewLandTileDataMul)Marshal.PtrToStructure(ptr, typeof(NewLandTileDataMul));
+ LandTable[i + count] = new LandData(cur);
+ }
+ else
+ {
+ currentPos += sizeof(OldLandTileDataMul);
+ var cur = (OldLandTileDataMul)Marshal.PtrToStructure(ptr, typeof(OldLandTileDataMul));
+ LandTable[i + count] = new LandData(cur);
+ }
+ }
+ }
+
+ long remaining = buffer.Length - currentPos;
+
+ int structSize = useNeWTileDataFormat ? sizeof(NewItemTileDataMul) : sizeof(OldItemTileDataMul);
+
+ var itemHeader = new int[remaining / ((structSize * 32) + 4)];
+ int itemLength = itemHeader.Length * 32;
+
+ ItemTable = new ItemData[itemLength];
+ HeightTable = new int[itemLength];
+
+ j = 0;
+ for (int i = 0; i < itemLength; i += 32)
+ {
+ var ptrHeader = new IntPtr((long)gc.AddrOfPinnedObject() + currentPos);
+ currentPos += 4;
+ itemHeader[j++] = (int)Marshal.PtrToStructure(ptrHeader, typeof(int));
+ for (int count = 0; count < 32; ++count)
+ {
+ var ptr = new IntPtr((long)gc.AddrOfPinnedObject() + currentPos);
+ if (useNeWTileDataFormat)
+ {
+ currentPos += sizeof(NewItemTileDataMul);
+ var cur = (NewItemTileDataMul)Marshal.PtrToStructure(ptr, typeof(NewItemTileDataMul));
+ ItemTable[i + count] = new ItemData(cur);
+ HeightTable[i + count] = cur.height;
+ }
+ else
+ {
+ currentPos += sizeof(OldItemTileDataMul);
+ var cur = (OldItemTileDataMul)Marshal.PtrToStructure(ptr, typeof(OldItemTileDataMul));
+ ItemTable[i + count] = new ItemData(cur);
+ HeightTable[i + count] = cur.height;
+ }
+ }
+ }
+ }
+ finally
+ {
+ gc.Free();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/UoFiddler.Plugin.Compare/Classes/TileDataCompareOptions.cs b/UoFiddler.Plugin.Compare/Classes/TileDataCompareOptions.cs
new file mode 100644
index 00000000..cb59658a
--- /dev/null
+++ b/UoFiddler.Plugin.Compare/Classes/TileDataCompareOptions.cs
@@ -0,0 +1,60 @@
+using Ultima;
+
+namespace UoFiddler.Plugin.Compare.Classes
+{
+ ///
+ /// Controls which fields and flags participate in TileData comparison.
+ /// All fields included and no flags ignored by default.
+ ///
+ public class TileDataCompareOptions
+ {
+ // ── Land tile fields ─────────────────────────────────────────────────────
+ public bool LandCompareName { get; set; } = true;
+ public bool LandCompareTextureId { get; set; } = true;
+ public bool LandCompareFlags { get; set; } = true;
+
+ // ── Item tile fields ─────────────────────────────────────────────────────
+ public bool ItemCompareName { get; set; } = true;
+ public bool ItemCompareFlags { get; set; } = true;
+ public bool ItemCompareAnimation { get; set; } = true;
+ public bool ItemCompareWeight { get; set; } = true;
+ public bool ItemCompareQuality { get; set; } = true;
+ public bool ItemCompareQuantity { get; set; } = true;
+ public bool ItemCompareHue { get; set; } = true;
+ public bool ItemCompareStackingOffset { get; set; } = true;
+ public bool ItemCompareValue { get; set; } = true;
+ public bool ItemCompareHeight { get; set; } = true;
+ public bool ItemCompareMiscData { get; set; } = true;
+ public bool ItemCompareUnk2 { get; set; } = true;
+ public bool ItemCompareUnk3 { get; set; } = true;
+
+ // ── Flag exclusions ──────────────────────────────────────────────────────
+ ///
+ /// Flags OR'd into this mask are excluded from comparison for both land and item tiles.
+ ///
+ public TileFlag IgnoredFlags { get; set; } = TileFlag.None;
+
+ public void ResetToDefaults()
+ {
+ LandCompareName = true;
+ LandCompareTextureId = true;
+ LandCompareFlags = true;
+
+ ItemCompareName = true;
+ ItemCompareFlags = true;
+ ItemCompareAnimation = true;
+ ItemCompareWeight = true;
+ ItemCompareQuality = true;
+ ItemCompareQuantity = true;
+ ItemCompareHue = true;
+ ItemCompareStackingOffset = true;
+ ItemCompareValue = true;
+ ItemCompareHeight = true;
+ ItemCompareMiscData = true;
+ ItemCompareUnk2 = true;
+ ItemCompareUnk3 = true;
+
+ IgnoredFlags = TileFlag.None;
+ }
+ }
+}
diff --git a/UoFiddler.Plugin.Compare/Classes/Utils.cs b/UoFiddler.Plugin.Compare/Classes/Utils.cs
new file mode 100644
index 00000000..bf5e14ad
--- /dev/null
+++ b/UoFiddler.Plugin.Compare/Classes/Utils.cs
@@ -0,0 +1,920 @@
+/***************************************************************************
+ *
+ * $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.Drawing;
+using System.Drawing.Imaging;
+using System.Globalization;
+using System.IO;
+
+namespace UoFiddler.Plugin.Compare.Classes
+{
+ public static class Utils
+ {
+ ///
+ /// The color key auto-detection filter.
+ /// The color key is determined by the most common color of the 4x corner pixels.
+ ///
+ /// Image in 32-bit R8G8B8 format
+ /// Filtered image
+ public static unsafe Bitmap CKeyFilter(Bitmap bmp)
+ {
+ BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
+ uint* line = (uint*)bd.Scan0;
+ int delta = bd.Stride >> 2;
+ uint* line2 = line + delta * (bmp.Height - 1);
+
+ Bitmap newBmp = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format32bppRgb);
+ BitmapData newBitmapData = newBmp.LockBits(new Rectangle(0, 0, newBmp.Width, newBmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
+
+ uint* newLine = (uint*)newBitmapData.Scan0;
+ int newDelta = newBitmapData.Stride >> 2;
+
+ uint colorKey;
+
+ uint colorKey1 = line[0];
+ uint colorKey2 = line[bmp.Width - 1];
+ uint colorKey3 = line2[0];
+ uint colorKey4 = line2[bmp.Width - 1];
+
+ if (colorKey1 == colorKey2 || colorKey1 == colorKey3 || colorKey1 == colorKey4)
+ {
+ colorKey = colorKey1;
+ }
+ else if (colorKey2 == colorKey3 || colorKey2 == colorKey4)
+ {
+ colorKey = colorKey2;
+ }
+ else if (colorKey3 == colorKey4)
+ {
+ colorKey = colorKey3;
+ }
+ else
+ {
+ colorKey = colorKey4;
+ }
+
+ for (int y = 0; y < bmp.Height; ++y, line += delta, newLine += newDelta)
+ {
+ uint* current = line;
+ uint* currentNew = newLine;
+
+ for (int x = 0; x < bmp.Width; ++x)
+ {
+ if (current[x] == colorKey)
+ {
+ currentNew[x] = 0;
+ }
+ else
+ {
+ currentNew[x] = current[x];
+ }
+ }
+ }
+
+ bmp.UnlockBits(bd);
+ newBmp.UnlockBits(newBitmapData);
+
+ return newBmp;
+ }
+
+ ///
+ /// Black half-tone correction filter.
+ /// Brightens the black halftones to 8, 8, 8. To ensure that black halftones are not mistaken for a color key when converting to R5G5B5.
+ ///
+ /// Image in 32-bit R8G8B8 format
+ /// If true, it lightens the color key to 8, 8, 8.
+ /// Filtered image
+ public static unsafe Bitmap BColFilter(Bitmap bmp, bool forceCCol2BCol)
+ {
+ BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
+ uint* line = (uint*)bd.Scan0;
+ int delta = bd.Stride >> 2;
+
+ Bitmap bmpnew = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format32bppRgb);
+ BitmapData bdnew = bmpnew.LockBits(new Rectangle(0, 0, bmpnew.Width, bmpnew.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
+
+ uint* linenew = (uint*)bdnew.Scan0;
+ int deltanew = bdnew.Stride >> 2;
+
+ for (int Y = 0; Y < bmp.Height; ++Y, line += delta, linenew += deltanew)
+ {
+ uint* cur = line;
+ uint* curnew = linenew;
+ for (int X = 0; X < bmp.Width; ++X)
+ {
+ byte a = (byte)((cur[X] & 0xFF000000) >> 24);
+ byte r = (byte)((cur[X] & 0x00FF0000) >> 16);
+ byte g = (byte)((cur[X] & 0x0000FF00) >> 8);
+ byte b = (byte)((cur[X] & 0x000000FF) >> 0);
+
+ if (r < 8 && g < 8 && b < 8)
+ {
+ byte max = Math.Max(r, Math.Max(g, b));
+ if (!forceCCol2BCol)
+ {
+ if (r != 0 && r == max)
+ {
+ curnew[X] |= 0xFF080000;
+ }
+
+ if (g != 0 && g == max)
+ {
+ curnew[X] |= 0xFF000800;
+ }
+
+ if (b != 0 && b == max)
+ {
+ curnew[X] |= 0xFF000008;
+ }
+ }
+ else
+ {
+ if (r == max)
+ {
+ curnew[X] |= 0xFF080000;
+ }
+
+ if (g == max)
+ {
+ curnew[X] |= 0xFF000800;
+ }
+
+ if (b == max)
+ {
+ curnew[X] |= 0xFF000008;
+ }
+ }
+ }
+ else
+ {
+ curnew[X] = cur[X];
+ }
+ }
+ }
+ bmp.UnlockBits(bd);
+ bmpnew.UnlockBits(bdnew);
+ return bmpnew;
+ }
+
+ ///
+ /// Фильтр коррекции белых полутанов.
+ /// Затемняет белые полутона до 247, 247, 247. Для того чтобы при конвертировании в R5G5B5 белые полутона небыли ошибочно восприняты как цветовой ключ.
+ ///
+ /// Изображение в 32х битном формате R8G8B8
+ /// Если true то осветляет цветовой ключ до 247, 247, 247.
+ /// Filtered image
+ public static unsafe Bitmap WColFilter(Bitmap bmp, bool forceCCol2WCol)
+ {
+ BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
+ uint* line = (uint*)bd.Scan0;
+ int delta = bd.Stride >> 2;
+
+ Bitmap bmpnew = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format32bppRgb);
+ BitmapData bdnew = bmpnew.LockBits(new Rectangle(0, 0, bmpnew.Width, bmpnew.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
+
+ uint* linenew = (uint*)bdnew.Scan0;
+ int deltanew = bdnew.Stride >> 2;
+
+ for (int Y = 0; Y < bmp.Height; ++Y, line += delta, linenew += deltanew)
+ {
+ uint* cur = line;
+ uint* curnew = linenew;
+ for (int X = 0; X < bmp.Width; ++X)
+ {
+ byte a = (byte)((cur[X] & 0xFF000000) >> 24);
+ byte r = (byte)((cur[X] & 0x00FF0000) >> 16);
+ byte g = (byte)((cur[X] & 0x0000FF00) >> 8);
+ byte b = (byte)((cur[X] & 0x000000FF) >> 0);
+
+ if (r > 247 && g > 247 && b > 247)
+ {
+ byte min = Math.Min(r, Math.Max(g, b));
+ if (!forceCCol2WCol)
+ {
+ if (r != 255 && r == min)
+ {
+ curnew[X] |= 0xFFF70000;
+ }
+ else
+ {
+ curnew[X] |= ((uint)r << 16);
+ }
+
+ if (g != 255 && g == min)
+ {
+ curnew[X] |= 0xFF00F700;
+ }
+ else
+ {
+ curnew[X] |= ((uint)g << 8);
+ }
+
+ if (b != 255 && b == min)
+ {
+ curnew[X] |= 0xFF0000F7;
+ }
+ else
+ {
+ curnew[X] |= ((uint)b << 0);
+ }
+ }
+ else
+ {
+ if (r == min)
+ {
+ curnew[X] |= 0xFFF70000;
+ }
+ else
+ {
+ curnew[X] |= ((uint)r << 16);
+ }
+
+ if (g == min)
+ {
+ curnew[X] |= 0xFF00F700;
+ }
+ else
+ {
+ curnew[X] |= ((uint)g << 8);
+ }
+
+ if (b == min)
+ {
+ curnew[X] |= 0xFF0000F7;
+ }
+ else
+ {
+ curnew[X] |= ((uint)b << 0);
+ }
+ }
+ }
+ else
+ {
+ curnew[X] = cur[X];
+ }
+ }
+ }
+ bmp.UnlockBits(bd);
+ bmpnew.UnlockBits(bdnew);
+ return bmpnew;
+ }
+
+ ///
+ /// Фильтр для стирания нижних границ тайла для убирания эфекта "сетки" при использовании тайла с флагом Translucent.
+ /// Входящее изображение должно иметь ширину 44, иначе фильтр возвратит передаенное ему изображение.
+ ///
+ /// Изображение в 32х битном формате R8G8B8
+ /// Если true то тайлы с высотой отличной от 44 не преобразуются.
+ /// Filtered image
+ public static unsafe Bitmap CuttingRawBmpForTranslucent(Bitmap bmp, bool onlyraw = true)
+ {
+ if ((bmp.Height != 0 && bmp.Width != 44) || (onlyraw && bmp.Height != 44))
+ {
+ return bmp;
+ }
+
+ BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
+ uint* line = (uint*)bd.Scan0;
+ int delta = bd.Stride >> 2;
+
+ Bitmap bmpnew = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format32bppRgb);
+ BitmapData bdnew = bmpnew.LockBits(new Rectangle(0, 0, bmpnew.Width, bmpnew.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
+ uint* linenew = (uint*)bdnew.Scan0;
+ int deltanew = bdnew.Stride >> 2;
+
+ int y0 = Math.Max(0, bmp.Height - 22);
+ //line += y0 * delta;
+ //linenew += y0 * deltanew;
+
+ int x1 = y0 > 0 ? 0 : 22 - bmp.Height;
+ int x2 = y0 > 0 ? 43 : 43 - x1;
+
+ for (int Y = 0; Y < y0; ++Y, line += delta, linenew += deltanew)
+ {
+ uint* cur = line;
+ uint* curnew = linenew;
+ for (int X = 0; X < bmp.Width; ++X)
+ curnew[X] = cur[X];
+ }
+ line = (uint*)bd.Scan0 + y0 * delta;
+ linenew = (uint*)bdnew.Scan0 + y0 * deltanew;
+ for (int Y = y0; Y < bmp.Height; ++Y, line += delta, linenew += deltanew)
+ {
+ uint* cur = line;
+ uint* curnew = linenew;
+ for (int X = 0; X < bmp.Width; ++X)
+ {
+ if (X != x1 && X != x2)
+ {
+ curnew[X] = cur[X];
+ }
+ else
+ {
+ curnew[X] = unchecked((ushort)0xFF000000);
+ }
+ }
+ ++x1;
+ --x2;
+ }
+ bmp.UnlockBits(bd);
+ bmpnew.UnlockBits(bdnew);
+ return bmpnew;
+ }
+
+ ///
+ /// Вычисляет средний цвет в изображении.
+ /// Подсчет введется как среднее арифмитическое составляющие всех цветов, кроме абсолютно черного (0,0,0).
+ ///
+ /// Изображение в 32х битном формате R8G8B8 или 16 битном A1R5G5B5.
+ /// Если true то осветляет возвращаемый результат до (8,8,8) если он равен (0,0,0).
+ /// Hue - цвет в 16 битном формате.
+ public static unsafe ushort AverageCol(Bitmap bmp, bool noneBlack = true)
+ {
+ ulong r = 0;
+ ulong g = 0;
+ ulong b = 0;
+ ulong count = 0;
+
+ if (bmp.PixelFormat == PixelFormat.Format32bppRgb)
+ {
+ BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
+ uint* line = (uint*)bd.Scan0;
+ int delta = bd.Stride >> 2;
+ for (int Y = 0; Y < bmp.Height; ++Y, line += delta)
+ {
+ uint* cur = line;
+ for (int X = 0; X < bmp.Width; ++X)
+ {
+ if ((uint)(cur[X] & 0x00FFFFFF) == 0)
+ {
+ continue;
+ }
+
+ r += (ulong)((cur[X] & 0x00FF0000) >> 16);
+ g += (ulong)((cur[X] & 0x0000FF00) >> 8);
+ b += (ulong)((cur[X] & 0x000000FF) >> 0);
+ ++count;
+ }
+ }
+ bmp.UnlockBits(bd);
+ }
+ else if (bmp.PixelFormat == PixelFormat.Format16bppArgb1555)
+ {
+ BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format16bppArgb1555);
+ ushort* line = (ushort*)bd.Scan0;
+ int delta = bd.Stride >> 1;
+ for (int Y = 0; Y < bmp.Height; ++Y, line += delta)
+ {
+ ushort* cur = line;
+ for (int X = 0; X < bmp.Width; ++X)
+ {
+ if ((ushort)(cur[X] & 0x7FFF) == 0)
+ {
+ continue;
+ }
+
+ r += (ulong)((cur[X] & 0x7C00) >> 10);
+ g += (ulong)((cur[X] & 0x03E0) >> 5);
+ b += (ulong)((cur[X] & 0x001F) >> 0);
+ ++count;
+ }
+ }
+ bmp.UnlockBits(bd);
+ }
+ else
+ {
+ throw new ArgumentException("Неподдерживываемый формат пикселя");
+ }
+
+ r = (ulong)Math.Round(((double)r / (double)count));
+ g = (ulong)Math.Round(((double)g / (double)count));
+ b = (ulong)Math.Round(((double)b / (double)count));
+
+ ushort hue = 0x0421;
+ if (bmp.PixelFormat == PixelFormat.Format32bppRgb)
+ {
+ hue = Ultima.Hues.ColorToHue(Color.FromArgb((int)r, (int)g, (int)b));
+ }
+ else if (bmp.PixelFormat == PixelFormat.Format16bppArgb1555)
+ {
+ hue = (ushort)((r << 10) | (g << 5) | (b));
+ }
+
+ if (noneBlack && (ushort)(hue & 0x7FFF) == 0)
+ {
+ hue = 0x0421;
+ }
+
+ return hue;
+ }
+
+ public static bool CompareBitmaps(Bitmap bmp10, Bitmap bmp20)
+ {
+ Bitmap bmp1 = new Bitmap(bmp10);
+ Bitmap bmp2 = new Bitmap(bmp20);
+ if (bmp1.Width != bmp2.Width || bmp1.Height != bmp2.Height)
+ {
+ return false;
+ }
+
+ ImageLockMode Mode = ImageLockMode.ReadWrite;
+ Rectangle Range = new Rectangle(0, 0, bmp1.Width, bmp1.Height);
+ BitmapData BMPD1 = bmp1.LockBits(Range, Mode, bmp1.PixelFormat);
+ BitmapData BMPD2 = bmp2.LockBits(Range, Mode, bmp2.PixelFormat);
+ bool result = true;
+ try
+ {
+ unsafe
+ {
+ byte* p1 = (byte*)(void*)BMPD1.Scan0;
+ byte* p2 = (byte*)(void*)BMPD2.Scan0;
+
+ int c = Range.Height * BMPD1.Stride;
+ for (int i = 0; i < c; i++)
+ {
+ if (*p1 != *p2)
+ {
+ result = false;
+ break;
+ }
+ p1++;
+ p2++;
+ }
+ }
+ }
+ finally
+ {
+
+ }
+
+ bmp1.UnlockBits(BMPD1);
+ bmp2.UnlockBits(BMPD2);
+ return result;
+ }
+
+ public static unsafe Bitmap ConvertBmp(Bitmap bmp)
+ {
+ BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format16bppArgb1555);
+ ushort* line = (ushort*)bd.Scan0;
+ int delta = bd.Stride >> 1;
+
+ Bitmap bmpnew = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format16bppArgb1555);
+ BitmapData bdnew = bmpnew.LockBits(new Rectangle(0, 0, bmpnew.Width, bmpnew.Height), ImageLockMode.WriteOnly, PixelFormat.Format16bppArgb1555);
+
+ ushort* linenew = (ushort*)bdnew.Scan0;
+ int deltanew = bdnew.Stride >> 1;
+
+ for (int Y = 0; Y < bmp.Height; ++Y, line += delta, linenew += deltanew)
+ {
+ ushort* cur = line;
+ ushort* curnew = linenew;
+ for (int X = 0; X < bmp.Width; ++X)
+ {
+ if ((cur[X] != 32768) && (cur[X] != 65535)) //True Black/White
+ {
+ curnew[X] = cur[X];
+ }
+ }
+ }
+ bmp.UnlockBits(bd);
+ bmpnew.UnlockBits(bdnew);
+ return bmpnew;
+ }
+
+ public static unsafe Bitmap ConvertBmpAnim(Bitmap bmp, int Red, int Green, int Blue)
+ {
+ //Extra background
+ int ExtraBack;
+ ExtraBack = (Red / 8) * 1024 + (Green / 8) * 32 + (Blue / 8) + 32768;
+ //
+ BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format16bppArgb1555);
+ ushort* line = (ushort*)bd.Scan0;
+ int delta = bd.Stride >> 1;
+
+ Bitmap bmpnew = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format16bppArgb1555);
+ BitmapData bdnew = bmpnew.LockBits(new Rectangle(0, 0, bmpnew.Width, bmpnew.Height), ImageLockMode.WriteOnly, PixelFormat.Format16bppArgb1555);
+
+ ushort* linenew = (ushort*)bdnew.Scan0;
+ int deltanew = bdnew.Stride >> 1;
+
+ for (int Y = 0; Y < bmp.Height; ++Y, line += delta, linenew += deltanew)
+ {
+ ushort* cur = line;
+ ushort* curnew = linenew;
+ for (int X = 0; X < bmp.Width; ++X)
+ {
+ //My Soulblighter Modification
+ // Convert color 0,0,0 to 0,0,8
+ //if ((cur[X] == 32768))
+ // curnew[X] = 32769;
+ //if ((cur[X] != 65535 & cur[X] != ExtraBack & cur[X] > 32768)) //True White == BackGround
+ // curnew[X] = cur[X];
+ //End of Soulblighter Modification
+ }
+ }
+ bmp.UnlockBits(bd);
+ bmpnew.UnlockBits(bdnew);
+ return bmpnew;
+ }
+
+ public static unsafe Bitmap ConvertBmpAnimCV5(Bitmap bmp, int Red, int Green, int Blue)
+ {
+ //Extra background
+ int ExtraBack;
+ ExtraBack = (Red / 8) * 1024 + (Green / 8) * 32 + (Blue / 8) + 32768;
+ //
+ BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format16bppArgb1555);
+ ushort* line = (ushort*)bd.Scan0;
+ int delta = bd.Stride >> 1;
+
+ Bitmap bmpnew = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format16bppArgb1555);
+ BitmapData bdnew = bmpnew.LockBits(new Rectangle(0, 0, bmpnew.Width, bmpnew.Height), ImageLockMode.WriteOnly, PixelFormat.Format16bppArgb1555);
+
+ ushort* linenew = (ushort*)bdnew.Scan0;
+ int deltanew = bdnew.Stride >> 1;
+
+ for (int Y = 0; Y < bmp.Height; ++Y, line += delta, linenew += deltanew)
+ {
+ ushort* cur = line;
+ ushort* curnew = linenew;
+ for (int X = 0; X < bmp.Width; ++X)
+ {
+ //My Soulblighter Modification
+ // Convert color 0,0,0 to 0,0,8
+ //if ((cur[X] == 32768))
+ // curnew[X] = 32769;
+ //if ((cur[X] != 65535 & cur[X] != 54965 & cur[X] != ExtraBack & cur[X] > 32768)) //True White == BackGround
+ // curnew[X] = cur[X];
+ //End of Soulblighter Modification
+ }
+ }
+ bmp.UnlockBits(bd);
+ bmpnew.UnlockBits(bdnew);
+ return bmpnew;
+ }
+
+ public static unsafe Bitmap ConvertBmpAnimKR(Bitmap bmp, int Red, int Green, int Blue)
+ {
+ //Extra background
+ //int extraBack = (Red / 8) * 1024 + (Green / 8) * 32 + (Blue / 8) + 32768;
+ //
+
+ BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format16bppArgb1555);
+ ushort* line = (ushort*)bd.Scan0;
+ int delta = bd.Stride >> 1;
+
+ Bitmap bmpnew = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format16bppArgb1555);
+ BitmapData bdnew = bmpnew.LockBits(new Rectangle(0, 0, bmpnew.Width, bmpnew.Height), ImageLockMode.WriteOnly, PixelFormat.Format16bppArgb1555);
+
+ ushort* linenew = (ushort*)bdnew.Scan0;
+ int deltanew = bdnew.Stride >> 1;
+
+ for (int Y = 0; Y < bmp.Height; ++Y, line += delta, linenew += deltanew)
+ {
+ ushort* cur = line;
+ ushort* curnew = linenew;
+
+ for (int X = 0; X < bmp.Width; ++X)
+ {
+ //if (cur[X] != 53235)
+ //{
+ // Convert back to RGB
+ int BlueTemp = ((cur[X] - 32768) / 32);
+ BlueTemp = BlueTemp * 32;
+ BlueTemp = (cur[X] - 32768) - BlueTemp;
+
+ int GreenTemp = ((cur[X] - 32768) / 1024);
+ GreenTemp = GreenTemp * 1024;
+ GreenTemp = (cur[X] - 32768) - GreenTemp - BlueTemp;
+ GreenTemp = GreenTemp / 32;
+
+ int RedTemp = ((cur[X] - 32768) / 1024);
+
+ // remove green colors
+ if (GreenTemp > BlueTemp & GreenTemp > RedTemp & GreenTemp > 10)
+ {
+ cur[X] = 65535;
+ }
+ //}
+
+ //My Soulblighter Modification
+ // Convert color 0,0,0 to 0,0,8
+ //if ((cur[X] == 32768))
+ // curnew[X] = 32769;
+ //if ((cur[X] != 65535 & cur[X] != 54965 & cur[X] != ExtraBack & cur[X] > 32768)) //True White == BackGround
+ // curnew[X] = cur[X];
+ //End of Soulblighter Modification
+ }
+ }
+
+ bmp.UnlockBits(bd);
+ bmpnew.UnlockBits(bdnew);
+
+ return bmpnew;
+ }
+
+ private static ColorPalette GetColorPalette(uint nColors)
+ {
+ // Assume monochrome image.
+ PixelFormat bitscolordepth = PixelFormat.Format1bppIndexed;
+
+ // Determine number of colors.
+ if (nColors > 2)
+ {
+ bitscolordepth = PixelFormat.Format4bppIndexed;
+ }
+
+ if (nColors > 16)
+ {
+ bitscolordepth = PixelFormat.Format8bppIndexed;
+ }
+
+ // Make a new Bitmap object to get its Palette.
+ ColorPalette palette;
+
+ using (Bitmap bitmap = new Bitmap(1, 1, bitscolordepth))
+ {
+ palette = bitmap.Palette;
+ }
+
+ return palette;
+ }
+
+ private static ColorPalette GetColorPalette(uint nColors, Color[] usedColors)
+ {
+ PixelFormat bitscolordepth = nColors > 16
+ ? PixelFormat.Format8bppIndexed
+ : nColors > 2
+ ? PixelFormat.Format4bppIndexed
+ : PixelFormat.Format1bppIndexed;
+
+ Bitmap bitmap = new Bitmap(1, 1, bitscolordepth);
+ ColorPalette palette = bitmap.Palette;
+ //palette.Flags = 0x00000001; // The color values in the array contain information about the alpha component.
+ bitmap.Dispose();
+
+ Array.Sort(usedColors);
+
+ if (usedColors.Length > nColors)
+ {
+
+ }
+
+ palette.Entries[0] = Color.FromArgb(0, 0, 0, 0);
+
+ for (uint i = 1; i < nColors; i++)
+ {
+
+ uint intensity = i * 0xFF / (nColors - 1); // Even distribution.
+
+ // The GIF encoder makes the first entry in the palette
+ // that has a ZERO alpha the transparent color in the GIF.
+ // Pick the first one arbitrarily, for demonstration purposes.
+
+ //if (i == 0 && fTransparent) // Make this color index...
+ int alpha = 0;
+
+ // Create a gray scale for demonstration purposes.
+ // Otherwise, use your favorite color reduction algorithm
+ // and an optimum palette for that algorithm generated here.
+ // For example, a color histogram, or a median cut palette.
+ palette.Entries[i] = Color.FromArgb((int)alpha, (int)intensity, (int)intensity, (int)intensity);
+ }
+
+ return palette;
+ }
+
+ public static void SaveGifWithNewColorTable(Image image, string filename, uint nColors, bool fTransparent)
+ {
+ // GIF codec supports 256 colors maximum, monochrome minimum.
+ if (nColors > 256)
+ {
+ nColors = 256;
+ }
+
+ if (nColors < 2)
+ {
+ nColors = 2;
+ }
+
+ // Make a new 8-BPP indexed bitmap that is the same size as the source image.
+ int width = image.Width;
+ int height = image.Height;
+
+ // Always use PixelFormat8bppIndexed because that is the color
+ // table-based interface to the GIF codec.
+ Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
+
+ // Create a color palette big enough to hold the colors you want.
+ ColorPalette pal = GetColorPalette(nColors);
+
+ // Initialize a new color table with entries that are determined
+ // by some optimal palette-finding algorithm; for demonstration
+ // purposes, use a greyscale.
+ for (uint i = 0; i < nColors; i++)
+ {
+ uint alpha = 0xFF; // Colors are opaque.
+ uint intensity = i * 0xFF / (nColors - 1); // Even distribution.
+
+ // The GIF encoder makes the first entry in the palette
+ // that has a ZERO alpha the transparent color in the GIF.
+ // Pick the first one arbitrarily, for demonstration purposes.
+
+ if (i == 0 && fTransparent) // Make this color index...
+ {
+ alpha = 0; // Transparent
+ }
+
+ // Create a gray scale for demonstration purposes.
+ // Otherwise, use your favorite color reduction algorithm
+ // and an optimum palette for that algorithm generated here.
+ // For example, a color histogram, or a median cut palette.
+ pal.Entries[i] = Color.FromArgb((int)alpha,
+ (int)intensity,
+ (int)intensity,
+ (int)intensity);
+ }
+
+ // Set the palette into the new Bitmap object.
+ bitmap.Palette = pal;
+
+
+ // Use GetPixel below to pull out the color data of Image.
+ // Because GetPixel isn't defined on an Image, make a copy
+ // in a Bitmap instead. Make a new Bitmap that is the same size as the
+ // image that you want to export. Or, try to
+ // interpret the native pixel format of the image by using a LockBits
+ // call. Use PixelFormat32BppARGB so you can wrap a Graphics
+ // around it.
+ Bitmap bmpCopy = new Bitmap(width, height, PixelFormat.Format32bppArgb);
+ {
+ Graphics g = Graphics.FromImage(bmpCopy);
+
+ g.PageUnit = GraphicsUnit.Pixel;
+
+ // Transfer the Image to the Bitmap
+ g.DrawImage(image, 0, 0, width, height);
+
+ // g goes out of scope and is marked for garbage collection.
+ // Force it, just to keep things clean.
+ g.Dispose();
+ }
+
+ // Lock a rectangular portion of the bitmap for writing.
+ Rectangle rect = new Rectangle(0, 0, width, height);
+
+ BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
+
+ // Write to the temporary buffer that is provided by LockBits.
+ // Copy the pixels from the source image in this loop.
+ // Because you want an index, convert RGB to the appropriate
+ // palette index here.
+ IntPtr pixels = bitmapData.Scan0;
+
+ unsafe
+ {
+ // Get the pointer to the image bits.
+ // This is the unsafe operation.
+ byte* pBits;
+ if (bitmapData.Stride > 0)
+ {
+ pBits = (byte*)pixels.ToPointer();
+ }
+ else
+ // If the Stide is negative, Scan0 points to the last
+ // scanline in the buffer. To normalize the loop, obtain
+ // a pointer to the front of the buffer that is located
+ // (Height-1) scanlines previous.
+ {
+ pBits = (byte*)pixels.ToPointer() + bitmapData.Stride * (height - 1);
+ }
+
+ uint stride = (uint)Math.Abs(bitmapData.Stride);
+
+ for (uint row = 0; row < height; ++row)
+ {
+ for (uint col = 0; col < width; ++col)
+ {
+ // Map palette indexes for a gray scale.
+ // If you use some other technique to color convert,
+ // put your favorite color reduction algorithm here.
+ Color pixel; // The source pixel.
+
+ // The destination pixel.
+ // The pointer to the color index byte of the
+ // destination; this real pointer causes this
+ // code to be considered unsafe.
+ byte* p8bppPixel = pBits + row * stride + col;
+
+ pixel = bmpCopy.GetPixel((int)col, (int)row);
+
+ // Use luminance/chrominance conversion to get grayscale.
+ // Basically, turn the image into black and white TV.
+ // Do not calculate Cr or Cb because you
+ // discard the color anyway.
+ // Y = Red * 0.299 + Green * 0.587 + Blue * 0.114
+
+ // This expression is best as integer math for performance,
+ // however, because GetPixel listed earlier is the slowest
+ // part of this loop, the expression is left as
+ // floating point for clarity.
+
+ double luminance = (pixel.R * 0.299) +
+ (pixel.G * 0.587) +
+ (pixel.B * 0.114);
+
+ // Gray scale is an intensity map from black to white.
+ // Compute the index to the grayscale entry that
+ // approximates the luminance, and then round the index.
+ // Also, constrain the index choices by the number of
+ // colors to do, and then set that pixel's index to the
+ // byte value.
+ *p8bppPixel = (byte)(luminance * (nColors - 1) / 255 + 0.5);
+
+ } /* end loop for col */
+ } /* end loop for row */
+ } /* end unsafe */
+
+ // To commit the changes, unlock the portion of the bitmap.
+ bitmap.UnlockBits(bitmapData);
+
+ bitmap.Save(filename, ImageFormat.Gif);
+
+ // Bitmap goes out of scope here and is also marked for
+ // garbage collection.
+ // Pal is referenced by bitmap and goes away.
+ // BmpCopy goes out of scope here and is marked for garbage
+ // collection. Force it, because it is probably quite large.
+ // The same applies to bitmap.
+ bmpCopy.Dispose();
+ bitmap.Dispose();
+
+ }
+
+ public static void FileRename(string folder, int startindex)
+ {
+ if (!Directory.Exists(folder))
+ {
+ return;
+ }
+
+ var files = Directory.GetFiles(folder, "*.bmp", SearchOption.AllDirectories);
+ var dicts = new Dictionary(files.Length);
+ foreach (var file in files)
+ {
+ string str_id;
+ int int_id;
+
+ if (Path.GetFileNameWithoutExtension(file)[1] == '0' && Path.GetFileNameWithoutExtension(file)[2] == 'x')
+ {
+ str_id = Path.GetFileNameWithoutExtension(file).Substring(3);
+ if (int.TryParse(str_id, NumberStyles.HexNumber, null, out int_id))
+ {
+ dicts.Add(int_id, file);
+ }
+ }
+ else
+ {
+ str_id = Path.GetFileNameWithoutExtension(file).Substring(1);
+ if (int.TryParse(str_id, NumberStyles.Integer, null, out int_id))
+ {
+ dicts.Add(int_id, file);
+ }
+ }
+ }
+
+ var keys = new List(dicts.Keys);
+ keys.Sort();
+
+ foreach (int key in keys)
+ {
+ var outf = Path.Combine(Path.Combine(folder, "__RENAMED__"), Path.GetDirectoryName(dicts[key]).Substring(folder.Length));
+ var outn = $"{Path.GetFileName(dicts[key])[0]}0x{++startindex:X2}.{Path.GetExtension(dicts[key])}";
+ Directory.CreateDirectory(outf);
+ File.Move(dicts[key], Path.Combine(outf, outn));
+ }
+ }
+ }
+
+ //public class MyEventArgs : EventArgs
+ //{
+ // public enum TYPES
+ // {
+ // COMMON = 0,
+ // FORCERELOAD
+ // }
+ // public TYPES Type { get; private set; }
+ // public MyEventArgs() { Type = TYPES.COMMON; }
+ // public MyEventArgs(TYPES type) { Type = type; }
+ //}
+}
diff --git a/UoFiddler.Plugin.Compare/ComparePluginBase.cs b/UoFiddler.Plugin.Compare/ComparePluginBase.cs
index 237bb11f..dbb4f494 100644
--- a/UoFiddler.Plugin.Compare/ComparePluginBase.cs
+++ b/UoFiddler.Plugin.Compare/ComparePluginBase.cs
@@ -34,7 +34,9 @@ public class ComparePluginBase : PluginBase
+ "Compares 2 Map files\r\n"
+ "Compares 2 Gump files\r\n"
+ "Compares 2 Texture files\r\n"
- + "(Adds 7 new Tabs)";
+ + "Compares 2 AnimData files\r\n"
+ + "Compares 2 RadarCol files\r\n"
+ + "(Adds 9 new Tabs)";
///
/// Author of the plugin
@@ -133,6 +135,7 @@ public override void ModifyTabPages(TabControl tabControl)
};
page6.Controls.Add(compM);
tabControl.TabPages.Add(page6);
+
TabPage page7 = new TabPage
{
Tag = tabControl.TabCount + 1,
@@ -144,6 +147,42 @@ public override void ModifyTabPages(TabControl tabControl)
};
page7.Controls.Add(compTextureControl);
tabControl.TabPages.Add(page7);
+
+ TabPage page8 = new TabPage
+ {
+ Tag = tabControl.TabCount + 1,
+ Text = "Compare AnimData"
+ };
+ CompareAnimDataControl compAnimData = new CompareAnimDataControl
+ {
+ Dock = DockStyle.Fill
+ };
+ page8.Controls.Add(compAnimData);
+ tabControl.TabPages.Add(page8);
+
+ TabPage page9 = new TabPage
+ {
+ Tag = tabControl.TabCount + 1,
+ Text = "Compare RadarCol"
+ };
+ CompareRadarColControl compRadarCol = new CompareRadarColControl
+ {
+ Dock = DockStyle.Fill
+ };
+ page9.Controls.Add(compRadarCol);
+ tabControl.TabPages.Add(page9);
+
+ TabPage page10 = new TabPage
+ {
+ Tag = tabControl.TabCount + 1,
+ Text = "Compare TileData"
+ };
+ CompareTileDataControl compTileDataControl = new CompareTileDataControl
+ {
+ Dock = DockStyle.Fill
+ };
+ page10.Controls.Add(compTileDataControl);
+ tabControl.TabPages.Add(page10);
}
public override void ModifyPluginToolStrip(ToolStripDropDownButton toolStrip)
diff --git a/UoFiddler.Plugin.Compare/UoFiddler.Plugin.Compare.csproj b/UoFiddler.Plugin.Compare/UoFiddler.Plugin.Compare.csproj
index 58436a67..67a48ff1 100644
--- a/UoFiddler.Plugin.Compare/UoFiddler.Plugin.Compare.csproj
+++ b/UoFiddler.Plugin.Compare/UoFiddler.Plugin.Compare.csproj
@@ -69,6 +69,18 @@
CompareTextureControl.cs
+
+ UserControl
+
+
+ CompareAnimDataControl.cs
+
+
+ UserControl
+
+
+ CompareRadarColControl.cs
+
True
True
@@ -107,6 +119,12 @@
CompareTextureControl.cs
+
+ CompareAnimDataControl.cs
+
+
+ CompareRadarColControl.cs
+
ResXFileCodeGenerator
Resources.Designer.cs
diff --git a/UoFiddler.Plugin.Compare/UserControls/CompareAnimDataControl.Designer.cs b/UoFiddler.Plugin.Compare/UserControls/CompareAnimDataControl.Designer.cs
new file mode 100644
index 00000000..72337c24
--- /dev/null
+++ b/UoFiddler.Plugin.Compare/UserControls/CompareAnimDataControl.Designer.cs
@@ -0,0 +1,587 @@
+namespace UoFiddler.Plugin.Compare.UserControls
+{
+ partial class CompareAnimDataControl
+ {
+ private System.ComponentModel.IContainer components = null;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+ splitContainer1 = new System.Windows.Forms.SplitContainer();
+ tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
+ tileViewOrg = new UoFiddler.Controls.UserControls.TileView.TileViewControl();
+ panelDetail = new System.Windows.Forms.Panel();
+ groupBoxLegend = new System.Windows.Forms.GroupBox();
+ legendSwatchOnlyInOrg = new System.Windows.Forms.Label();
+ legendLabelOnlyInOrg = new System.Windows.Forms.Label();
+ legendSwatchOnlyInSecond = new System.Windows.Forms.Label();
+ legendLabelOnlyInSecond = new System.Windows.Forms.Label();
+ legendSwatchDifferent = new System.Windows.Forms.Label();
+ legendLabelDifferent = new System.Windows.Forms.Label();
+ legendSwatchIdentical = new System.Windows.Forms.Label();
+ legendLabelIdentical = new System.Windows.Forms.Label();
+ groupBoxSec = new System.Windows.Forms.GroupBox();
+ labelSecFrameCountCaption = new System.Windows.Forms.Label();
+ labelSecFrameIntervalCaption = new System.Windows.Forms.Label();
+ labelSecFrameStartCaption = new System.Windows.Forms.Label();
+ labelSecFrameDataCaption = new System.Windows.Forms.Label();
+ labelSecFrameCount = new System.Windows.Forms.Label();
+ labelSecFrameInterval = new System.Windows.Forms.Label();
+ labelSecFrameStart = new System.Windows.Forms.Label();
+ labelSecFrameData = new System.Windows.Forms.Label();
+ groupBoxOrg = new System.Windows.Forms.GroupBox();
+ labelOrgFrameCountCaption = new System.Windows.Forms.Label();
+ labelOrgFrameIntervalCaption = new System.Windows.Forms.Label();
+ labelOrgFrameStartCaption = new System.Windows.Forms.Label();
+ labelOrgFrameDataCaption = new System.Windows.Forms.Label();
+ labelOrgFrameCount = new System.Windows.Forms.Label();
+ labelOrgFrameInterval = new System.Windows.Forms.Label();
+ labelOrgFrameStart = new System.Windows.Forms.Label();
+ labelOrgFrameData = new System.Windows.Forms.Label();
+ tileViewSec = new UoFiddler.Controls.UserControls.TileView.TileViewControl();
+ contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(components);
+ copyEntryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ buttonCopyAddedOnly = new System.Windows.Forms.Button();
+ buttonCopyAllDiff = new System.Windows.Forms.Button();
+ buttonCopySelected = new System.Windows.Forms.Button();
+ checkBoxShowDiff = new System.Windows.Forms.CheckBox();
+ buttonLoadSecond = new System.Windows.Forms.Button();
+ buttonBrowse = new System.Windows.Forms.Button();
+ textBoxSecondFile = new System.Windows.Forms.TextBox();
+ ((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit();
+ splitContainer1.Panel1.SuspendLayout();
+ splitContainer1.Panel2.SuspendLayout();
+ splitContainer1.SuspendLayout();
+ tableLayoutPanel1.SuspendLayout();
+ panelDetail.SuspendLayout();
+ groupBoxLegend.SuspendLayout();
+ groupBoxSec.SuspendLayout();
+ groupBoxOrg.SuspendLayout();
+ contextMenuStrip1.SuspendLayout();
+ SuspendLayout();
+ //
+ // splitContainer1
+ //
+ splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
+ splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2;
+ splitContainer1.IsSplitterFixed = true;
+ splitContainer1.Location = new System.Drawing.Point(0, 0);
+ splitContainer1.Name = "splitContainer1";
+ splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal;
+ //
+ // splitContainer1.Panel1
+ //
+ splitContainer1.Panel1.Controls.Add(tableLayoutPanel1);
+ //
+ // splitContainer1.Panel2
+ //
+ splitContainer1.Panel2.Controls.Add(buttonCopyAddedOnly);
+ splitContainer1.Panel2.Controls.Add(buttonCopyAllDiff);
+ splitContainer1.Panel2.Controls.Add(buttonCopySelected);
+ splitContainer1.Panel2.Controls.Add(checkBoxShowDiff);
+ splitContainer1.Panel2.Controls.Add(buttonLoadSecond);
+ splitContainer1.Panel2.Controls.Add(buttonBrowse);
+ splitContainer1.Panel2.Controls.Add(textBoxSecondFile);
+ splitContainer1.Size = new System.Drawing.Size(900, 510);
+ splitContainer1.SplitterDistance = 454;
+ splitContainer1.SplitterWidth = 5;
+ splitContainer1.TabIndex = 0;
+ //
+ // tableLayoutPanel1
+ //
+ tableLayoutPanel1.ColumnCount = 3;
+ tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
+ tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
+ tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
+ tableLayoutPanel1.Controls.Add(tileViewOrg, 0, 0);
+ tableLayoutPanel1.Controls.Add(panelDetail, 1, 0);
+ tableLayoutPanel1.Controls.Add(tileViewSec, 2, 0);
+ tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
+ tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
+ tableLayoutPanel1.Name = "tableLayoutPanel1";
+ tableLayoutPanel1.RowCount = 1;
+ tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
+ tableLayoutPanel1.Size = new System.Drawing.Size(900, 454);
+ tableLayoutPanel1.TabIndex = 0;
+ //
+ // tileViewOrg
+ //
+ tileViewOrg.Dock = System.Windows.Forms.DockStyle.Fill;
+ tileViewOrg.Location = new System.Drawing.Point(3, 3);
+ tileViewOrg.Name = "tileViewOrg";
+ tileViewOrg.Size = new System.Drawing.Size(219, 448);
+ tileViewOrg.TabIndex = 0;
+ tileViewOrg.TileSize = new System.Drawing.Size(219, 15);
+ tileViewOrg.TileMargin = new System.Windows.Forms.Padding(0);
+ tileViewOrg.TilePadding = new System.Windows.Forms.Padding(0);
+ tileViewOrg.TileBorderWidth = 0f;
+ tileViewOrg.TileHighLightOpacity = 0.0;
+ tileViewOrg.DrawItem += new System.EventHandler(OnDrawItemOrg);
+ tileViewOrg.FocusSelectionChanged += new System.EventHandler(OnFocusChangedOrg);
+ tileViewOrg.SizeChanged += new System.EventHandler(OnTileViewSizeChanged);
+ //
+ // panelDetail
+ //
+ panelDetail.Controls.Add(groupBoxLegend);
+ panelDetail.Controls.Add(groupBoxSec);
+ panelDetail.Controls.Add(groupBoxOrg);
+ panelDetail.Dock = System.Windows.Forms.DockStyle.Fill;
+ panelDetail.Location = new System.Drawing.Point(228, 3);
+ panelDetail.Name = "panelDetail";
+ panelDetail.Size = new System.Drawing.Size(444, 448);
+ panelDetail.TabIndex = 1;
+ //
+ // groupBoxLegend
+ //
+ groupBoxLegend.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
+ groupBoxLegend.Controls.Add(legendSwatchOnlyInOrg);
+ groupBoxLegend.Controls.Add(legendLabelOnlyInOrg);
+ groupBoxLegend.Controls.Add(legendSwatchOnlyInSecond);
+ groupBoxLegend.Controls.Add(legendLabelOnlyInSecond);
+ groupBoxLegend.Controls.Add(legendSwatchDifferent);
+ groupBoxLegend.Controls.Add(legendLabelDifferent);
+ groupBoxLegend.Controls.Add(legendSwatchIdentical);
+ groupBoxLegend.Controls.Add(legendLabelIdentical);
+ groupBoxLegend.Location = new System.Drawing.Point(6, 310);
+ groupBoxLegend.Name = "groupBoxLegend";
+ groupBoxLegend.Size = new System.Drawing.Size(432, 125);
+ groupBoxLegend.TabIndex = 2;
+ groupBoxLegend.TabStop = false;
+ groupBoxLegend.Text = "Legend";
+ //
+ // legendSwatchOnlyInOrg
+ //
+ legendSwatchOnlyInOrg.BackColor = System.Drawing.Color.Orange;
+ legendSwatchOnlyInOrg.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+ legendSwatchOnlyInOrg.Location = new System.Drawing.Point(8, 22);
+ legendSwatchOnlyInOrg.Name = "legendSwatchOnlyInOrg";
+ legendSwatchOnlyInOrg.Size = new System.Drawing.Size(16, 16);
+ legendSwatchOnlyInOrg.TabIndex = 0;
+ //
+ // legendLabelOnlyInOrg
+ //
+ legendLabelOnlyInOrg.AutoSize = true;
+ legendLabelOnlyInOrg.Location = new System.Drawing.Point(30, 23);
+ legendLabelOnlyInOrg.Name = "legendLabelOnlyInOrg";
+ legendLabelOnlyInOrg.Size = new System.Drawing.Size(88, 15);
+ legendLabelOnlyInOrg.TabIndex = 1;
+ legendLabelOnlyInOrg.Text = "Only in original";
+ //
+ // legendSwatchOnlyInSecond
+ //
+ legendSwatchOnlyInSecond.BackColor = System.Drawing.Color.Green;
+ legendSwatchOnlyInSecond.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+ legendSwatchOnlyInSecond.Location = new System.Drawing.Point(8, 46);
+ legendSwatchOnlyInSecond.Name = "legendSwatchOnlyInSecond";
+ legendSwatchOnlyInSecond.Size = new System.Drawing.Size(16, 16);
+ legendSwatchOnlyInSecond.TabIndex = 2;
+ //
+ // legendLabelOnlyInSecond
+ //
+ legendLabelOnlyInSecond.AutoSize = true;
+ legendLabelOnlyInSecond.Location = new System.Drawing.Point(30, 47);
+ legendLabelOnlyInSecond.Name = "legendLabelOnlyInSecond";
+ legendLabelOnlyInSecond.Size = new System.Drawing.Size(86, 15);
+ legendLabelOnlyInSecond.TabIndex = 3;
+ legendLabelOnlyInSecond.Text = "Only in second";
+ //
+ // legendSwatchDifferent
+ //
+ legendSwatchDifferent.BackColor = System.Drawing.Color.Blue;
+ legendSwatchDifferent.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+ legendSwatchDifferent.Location = new System.Drawing.Point(8, 70);
+ legendSwatchDifferent.Name = "legendSwatchDifferent";
+ legendSwatchDifferent.Size = new System.Drawing.Size(16, 16);
+ legendSwatchDifferent.TabIndex = 4;
+ //
+ // legendLabelDifferent
+ //
+ legendLabelDifferent.AutoSize = true;
+ legendLabelDifferent.Location = new System.Drawing.Point(30, 71);
+ legendLabelDifferent.Name = "legendLabelDifferent";
+ legendLabelDifferent.Size = new System.Drawing.Size(89, 15);
+ legendLabelDifferent.TabIndex = 5;
+ legendLabelDifferent.Text = "Different values";
+ //
+ // legendSwatchIdentical
+ //
+ legendSwatchIdentical.BackColor = System.Drawing.Color.Gray;
+ legendSwatchIdentical.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+ legendSwatchIdentical.Location = new System.Drawing.Point(8, 96);
+ legendSwatchIdentical.Name = "legendSwatchIdentical";
+ legendSwatchIdentical.Size = new System.Drawing.Size(16, 16);
+ legendSwatchIdentical.TabIndex = 6;
+ //
+ // legendLabelIdentical
+ //
+ legendLabelIdentical.AutoSize = true;
+ legendLabelIdentical.Location = new System.Drawing.Point(30, 97);
+ legendLabelIdentical.Name = "legendLabelIdentical";
+ legendLabelIdentical.Size = new System.Drawing.Size(52, 15);
+ legendLabelIdentical.TabIndex = 7;
+ legendLabelIdentical.Text = "Identical";
+ //
+ // groupBoxSec
+ //
+ groupBoxSec.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
+ groupBoxSec.Controls.Add(labelSecFrameCountCaption);
+ groupBoxSec.Controls.Add(labelSecFrameIntervalCaption);
+ groupBoxSec.Controls.Add(labelSecFrameStartCaption);
+ groupBoxSec.Controls.Add(labelSecFrameDataCaption);
+ groupBoxSec.Controls.Add(labelSecFrameCount);
+ groupBoxSec.Controls.Add(labelSecFrameInterval);
+ groupBoxSec.Controls.Add(labelSecFrameStart);
+ groupBoxSec.Controls.Add(labelSecFrameData);
+ groupBoxSec.Location = new System.Drawing.Point(6, 158);
+ groupBoxSec.Name = "groupBoxSec";
+ groupBoxSec.Size = new System.Drawing.Size(432, 140);
+ groupBoxSec.TabIndex = 1;
+ groupBoxSec.TabStop = false;
+ groupBoxSec.Text = "Right (Second)";
+ //
+ // labelSecFrameCountCaption
+ //
+ labelSecFrameCountCaption.AutoSize = true;
+ labelSecFrameCountCaption.Location = new System.Drawing.Point(6, 22);
+ labelSecFrameCountCaption.Name = "labelSecFrameCountCaption";
+ labelSecFrameCountCaption.Size = new System.Drawing.Size(79, 15);
+ labelSecFrameCountCaption.TabIndex = 0;
+ labelSecFrameCountCaption.Text = "Frame Count:";
+ //
+ // labelSecFrameIntervalCaption
+ //
+ labelSecFrameIntervalCaption.AutoSize = true;
+ labelSecFrameIntervalCaption.Location = new System.Drawing.Point(6, 44);
+ labelSecFrameIntervalCaption.Name = "labelSecFrameIntervalCaption";
+ labelSecFrameIntervalCaption.Size = new System.Drawing.Size(85, 15);
+ labelSecFrameIntervalCaption.TabIndex = 2;
+ labelSecFrameIntervalCaption.Text = "Frame Interval:";
+ //
+ // labelSecFrameStartCaption
+ //
+ labelSecFrameStartCaption.AutoSize = true;
+ labelSecFrameStartCaption.Location = new System.Drawing.Point(6, 66);
+ labelSecFrameStartCaption.Name = "labelSecFrameStartCaption";
+ labelSecFrameStartCaption.Size = new System.Drawing.Size(70, 15);
+ labelSecFrameStartCaption.TabIndex = 4;
+ labelSecFrameStartCaption.Text = "Frame Start:";
+ //
+ // labelSecFrameDataCaption
+ //
+ labelSecFrameDataCaption.AutoSize = true;
+ labelSecFrameDataCaption.Location = new System.Drawing.Point(6, 88);
+ labelSecFrameDataCaption.Name = "labelSecFrameDataCaption";
+ labelSecFrameDataCaption.Size = new System.Drawing.Size(70, 15);
+ labelSecFrameDataCaption.TabIndex = 6;
+ labelSecFrameDataCaption.Text = "Frame Data:";
+ //
+ // labelSecFrameCount
+ //
+ labelSecFrameCount.AutoSize = true;
+ labelSecFrameCount.Location = new System.Drawing.Point(120, 22);
+ labelSecFrameCount.Name = "labelSecFrameCount";
+ labelSecFrameCount.Size = new System.Drawing.Size(12, 15);
+ labelSecFrameCount.TabIndex = 1;
+ labelSecFrameCount.Text = "-";
+ //
+ // labelSecFrameInterval
+ //
+ labelSecFrameInterval.AutoSize = true;
+ labelSecFrameInterval.Location = new System.Drawing.Point(120, 44);
+ labelSecFrameInterval.Name = "labelSecFrameInterval";
+ labelSecFrameInterval.Size = new System.Drawing.Size(12, 15);
+ labelSecFrameInterval.TabIndex = 3;
+ labelSecFrameInterval.Text = "-";
+ //
+ // labelSecFrameStart
+ //
+ labelSecFrameStart.AutoSize = true;
+ labelSecFrameStart.Location = new System.Drawing.Point(120, 66);
+ labelSecFrameStart.Name = "labelSecFrameStart";
+ labelSecFrameStart.Size = new System.Drawing.Size(12, 15);
+ labelSecFrameStart.TabIndex = 5;
+ labelSecFrameStart.Text = "-";
+ //
+ // labelSecFrameData
+ //
+ labelSecFrameData.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
+ labelSecFrameData.Location = new System.Drawing.Point(120, 88);
+ labelSecFrameData.Name = "labelSecFrameData";
+ labelSecFrameData.Size = new System.Drawing.Size(300, 44);
+ labelSecFrameData.TabIndex = 7;
+ labelSecFrameData.Text = "-";
+ //
+ // groupBoxOrg
+ //
+ groupBoxOrg.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
+ groupBoxOrg.Controls.Add(labelOrgFrameCountCaption);
+ groupBoxOrg.Controls.Add(labelOrgFrameIntervalCaption);
+ groupBoxOrg.Controls.Add(labelOrgFrameStartCaption);
+ groupBoxOrg.Controls.Add(labelOrgFrameDataCaption);
+ groupBoxOrg.Controls.Add(labelOrgFrameCount);
+ groupBoxOrg.Controls.Add(labelOrgFrameInterval);
+ groupBoxOrg.Controls.Add(labelOrgFrameStart);
+ groupBoxOrg.Controls.Add(labelOrgFrameData);
+ groupBoxOrg.Location = new System.Drawing.Point(6, 6);
+ groupBoxOrg.Name = "groupBoxOrg";
+ groupBoxOrg.Size = new System.Drawing.Size(432, 140);
+ groupBoxOrg.TabIndex = 0;
+ groupBoxOrg.TabStop = false;
+ groupBoxOrg.Text = "Left (Original)";
+ //
+ // labelOrgFrameCountCaption
+ //
+ labelOrgFrameCountCaption.AutoSize = true;
+ labelOrgFrameCountCaption.Location = new System.Drawing.Point(6, 22);
+ labelOrgFrameCountCaption.Name = "labelOrgFrameCountCaption";
+ labelOrgFrameCountCaption.Size = new System.Drawing.Size(79, 15);
+ labelOrgFrameCountCaption.TabIndex = 0;
+ labelOrgFrameCountCaption.Text = "Frame Count:";
+ //
+ // labelOrgFrameIntervalCaption
+ //
+ labelOrgFrameIntervalCaption.AutoSize = true;
+ labelOrgFrameIntervalCaption.Location = new System.Drawing.Point(6, 44);
+ labelOrgFrameIntervalCaption.Name = "labelOrgFrameIntervalCaption";
+ labelOrgFrameIntervalCaption.Size = new System.Drawing.Size(85, 15);
+ labelOrgFrameIntervalCaption.TabIndex = 2;
+ labelOrgFrameIntervalCaption.Text = "Frame Interval:";
+ //
+ // labelOrgFrameStartCaption
+ //
+ labelOrgFrameStartCaption.AutoSize = true;
+ labelOrgFrameStartCaption.Location = new System.Drawing.Point(6, 66);
+ labelOrgFrameStartCaption.Name = "labelOrgFrameStartCaption";
+ labelOrgFrameStartCaption.Size = new System.Drawing.Size(70, 15);
+ labelOrgFrameStartCaption.TabIndex = 4;
+ labelOrgFrameStartCaption.Text = "Frame Start:";
+ //
+ // labelOrgFrameDataCaption
+ //
+ labelOrgFrameDataCaption.AutoSize = true;
+ labelOrgFrameDataCaption.Location = new System.Drawing.Point(6, 88);
+ labelOrgFrameDataCaption.Name = "labelOrgFrameDataCaption";
+ labelOrgFrameDataCaption.Size = new System.Drawing.Size(70, 15);
+ labelOrgFrameDataCaption.TabIndex = 6;
+ labelOrgFrameDataCaption.Text = "Frame Data:";
+ //
+ // labelOrgFrameCount
+ //
+ labelOrgFrameCount.AutoSize = true;
+ labelOrgFrameCount.Location = new System.Drawing.Point(120, 22);
+ labelOrgFrameCount.Name = "labelOrgFrameCount";
+ labelOrgFrameCount.Size = new System.Drawing.Size(12, 15);
+ labelOrgFrameCount.TabIndex = 1;
+ labelOrgFrameCount.Text = "-";
+ //
+ // labelOrgFrameInterval
+ //
+ labelOrgFrameInterval.AutoSize = true;
+ labelOrgFrameInterval.Location = new System.Drawing.Point(120, 44);
+ labelOrgFrameInterval.Name = "labelOrgFrameInterval";
+ labelOrgFrameInterval.Size = new System.Drawing.Size(12, 15);
+ labelOrgFrameInterval.TabIndex = 3;
+ labelOrgFrameInterval.Text = "-";
+ //
+ // labelOrgFrameStart
+ //
+ labelOrgFrameStart.AutoSize = true;
+ labelOrgFrameStart.Location = new System.Drawing.Point(120, 66);
+ labelOrgFrameStart.Name = "labelOrgFrameStart";
+ labelOrgFrameStart.Size = new System.Drawing.Size(12, 15);
+ labelOrgFrameStart.TabIndex = 5;
+ labelOrgFrameStart.Text = "-";
+ //
+ // labelOrgFrameData
+ //
+ labelOrgFrameData.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
+ labelOrgFrameData.Location = new System.Drawing.Point(120, 88);
+ labelOrgFrameData.Name = "labelOrgFrameData";
+ labelOrgFrameData.Size = new System.Drawing.Size(300, 44);
+ labelOrgFrameData.TabIndex = 7;
+ labelOrgFrameData.Text = "-";
+ //
+ // tileViewSec
+ //
+ tileViewSec.ContextMenuStrip = contextMenuStrip1;
+ tileViewSec.Dock = System.Windows.Forms.DockStyle.Fill;
+ tileViewSec.Location = new System.Drawing.Point(678, 3);
+ tileViewSec.Name = "tileViewSec";
+ tileViewSec.Size = new System.Drawing.Size(219, 448);
+ tileViewSec.TabIndex = 2;
+ tileViewSec.TileSize = new System.Drawing.Size(219, 15);
+ tileViewSec.TileMargin = new System.Windows.Forms.Padding(0);
+ tileViewSec.TilePadding = new System.Windows.Forms.Padding(0);
+ tileViewSec.TileBorderWidth = 0f;
+ tileViewSec.TileHighLightOpacity = 0.0;
+ tileViewSec.DrawItem += new System.EventHandler(OnDrawItemSec);
+ tileViewSec.FocusSelectionChanged += new System.EventHandler(OnFocusChangedSec);
+ tileViewSec.SizeChanged += new System.EventHandler(OnTileViewSizeChanged);
+ tileViewSec.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(OnDoubleClickSec);
+ //
+ // contextMenuStrip1
+ //
+ contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { copyEntryToolStripMenuItem });
+ contextMenuStrip1.Name = "contextMenuStrip1";
+ contextMenuStrip1.Size = new System.Drawing.Size(165, 26);
+ //
+ // copyEntryToolStripMenuItem
+ //
+ copyEntryToolStripMenuItem.Name = "copyEntryToolStripMenuItem";
+ copyEntryToolStripMenuItem.Size = new System.Drawing.Size(164, 22);
+ copyEntryToolStripMenuItem.Text = "Copy Entry 2 to 1";
+ copyEntryToolStripMenuItem.Click += OnClickCopySelected;
+ //
+ // buttonCopyAddedOnly
+ //
+ buttonCopyAddedOnly.Location = new System.Drawing.Point(755, 11);
+ buttonCopyAddedOnly.Name = "buttonCopyAddedOnly";
+ buttonCopyAddedOnly.Size = new System.Drawing.Size(100, 27);
+ buttonCopyAddedOnly.TabIndex = 6;
+ buttonCopyAddedOnly.Text = "Copy Added Only";
+ buttonCopyAddedOnly.UseVisualStyleBackColor = true;
+ buttonCopyAddedOnly.Click += OnClickCopyAddedOnly;
+ //
+ // buttonCopyAllDiff
+ //
+ buttonCopyAllDiff.Location = new System.Drawing.Point(659, 11);
+ buttonCopyAllDiff.Name = "buttonCopyAllDiff";
+ buttonCopyAllDiff.Size = new System.Drawing.Size(90, 27);
+ buttonCopyAllDiff.TabIndex = 5;
+ buttonCopyAllDiff.Text = "Copy All Diff";
+ buttonCopyAllDiff.UseVisualStyleBackColor = true;
+ buttonCopyAllDiff.Click += OnClickCopyAllDiff;
+ //
+ // buttonCopySelected
+ //
+ buttonCopySelected.Location = new System.Drawing.Point(563, 11);
+ buttonCopySelected.Name = "buttonCopySelected";
+ buttonCopySelected.Size = new System.Drawing.Size(90, 27);
+ buttonCopySelected.TabIndex = 4;
+ buttonCopySelected.Text = "Copy Selected";
+ buttonCopySelected.UseVisualStyleBackColor = true;
+ buttonCopySelected.Click += OnClickCopySelected;
+ //
+ // checkBoxShowDiff
+ //
+ checkBoxShowDiff.AutoSize = true;
+ checkBoxShowDiff.Location = new System.Drawing.Point(408, 15);
+ checkBoxShowDiff.Name = "checkBoxShowDiff";
+ checkBoxShowDiff.Size = new System.Drawing.Size(143, 19);
+ checkBoxShowDiff.TabIndex = 3;
+ checkBoxShowDiff.Text = "Show only Differences";
+ checkBoxShowDiff.UseVisualStyleBackColor = true;
+ checkBoxShowDiff.Click += OnChangeShowDiff;
+ //
+ // buttonLoadSecond
+ //
+ buttonLoadSecond.AutoSize = true;
+ buttonLoadSecond.Location = new System.Drawing.Point(306, 11);
+ buttonLoadSecond.Name = "buttonLoadSecond";
+ buttonLoadSecond.Size = new System.Drawing.Size(90, 27);
+ buttonLoadSecond.TabIndex = 2;
+ buttonLoadSecond.Text = "Load Second";
+ buttonLoadSecond.UseVisualStyleBackColor = true;
+ buttonLoadSecond.Click += OnClickLoadSecond;
+ //
+ // buttonBrowse
+ //
+ buttonBrowse.AutoSize = true;
+ buttonBrowse.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+ buttonBrowse.Location = new System.Drawing.Point(272, 13);
+ buttonBrowse.Name = "buttonBrowse";
+ buttonBrowse.Size = new System.Drawing.Size(26, 25);
+ buttonBrowse.TabIndex = 1;
+ buttonBrowse.Text = "...";
+ buttonBrowse.UseVisualStyleBackColor = true;
+ buttonBrowse.Click += OnClickBrowse;
+ //
+ // textBoxSecondFile
+ //
+ textBoxSecondFile.Location = new System.Drawing.Point(6, 15);
+ textBoxSecondFile.Name = "textBoxSecondFile";
+ textBoxSecondFile.Size = new System.Drawing.Size(260, 23);
+ textBoxSecondFile.TabIndex = 0;
+ //
+ // CompareAnimDataControl
+ //
+ AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ Controls.Add(splitContainer1);
+ DoubleBuffered = true;
+ Name = "CompareAnimDataControl";
+ Size = new System.Drawing.Size(900, 510);
+ Load += OnLoad;
+ splitContainer1.Panel1.ResumeLayout(false);
+ splitContainer1.Panel2.ResumeLayout(false);
+ splitContainer1.Panel2.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)splitContainer1).EndInit();
+ splitContainer1.ResumeLayout(false);
+ tableLayoutPanel1.ResumeLayout(false);
+ panelDetail.ResumeLayout(false);
+ groupBoxLegend.ResumeLayout(false);
+ groupBoxLegend.PerformLayout();
+ groupBoxSec.ResumeLayout(false);
+ groupBoxSec.PerformLayout();
+ groupBoxOrg.ResumeLayout(false);
+ groupBoxOrg.PerformLayout();
+ contextMenuStrip1.ResumeLayout(false);
+ ResumeLayout(false);
+ }
+
+ #endregion
+
+ private System.Windows.Forms.SplitContainer splitContainer1;
+ private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
+ private UoFiddler.Controls.UserControls.TileView.TileViewControl tileViewOrg;
+ private System.Windows.Forms.Panel panelDetail;
+ private System.Windows.Forms.GroupBox groupBoxOrg;
+ private System.Windows.Forms.Label labelOrgFrameCountCaption;
+ private System.Windows.Forms.Label labelOrgFrameCount;
+ private System.Windows.Forms.Label labelOrgFrameIntervalCaption;
+ private System.Windows.Forms.Label labelOrgFrameInterval;
+ private System.Windows.Forms.Label labelOrgFrameStartCaption;
+ private System.Windows.Forms.Label labelOrgFrameStart;
+ private System.Windows.Forms.Label labelOrgFrameDataCaption;
+ private System.Windows.Forms.Label labelOrgFrameData;
+ private System.Windows.Forms.GroupBox groupBoxSec;
+ private System.Windows.Forms.Label labelSecFrameCountCaption;
+ private System.Windows.Forms.Label labelSecFrameCount;
+ private System.Windows.Forms.Label labelSecFrameIntervalCaption;
+ private System.Windows.Forms.Label labelSecFrameInterval;
+ private System.Windows.Forms.Label labelSecFrameStartCaption;
+ private System.Windows.Forms.Label labelSecFrameStart;
+ private System.Windows.Forms.Label labelSecFrameDataCaption;
+ private System.Windows.Forms.Label labelSecFrameData;
+ private UoFiddler.Controls.UserControls.TileView.TileViewControl tileViewSec;
+ private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;
+ private System.Windows.Forms.ToolStripMenuItem copyEntryToolStripMenuItem;
+ private System.Windows.Forms.TextBox textBoxSecondFile;
+ private System.Windows.Forms.Button buttonBrowse;
+ private System.Windows.Forms.Button buttonLoadSecond;
+ private System.Windows.Forms.CheckBox checkBoxShowDiff;
+ private System.Windows.Forms.Button buttonCopySelected;
+ private System.Windows.Forms.Button buttonCopyAllDiff;
+ private System.Windows.Forms.Button buttonCopyAddedOnly;
+ private System.Windows.Forms.GroupBox groupBoxLegend;
+ private System.Windows.Forms.Label legendSwatchOnlyInOrg;
+ private System.Windows.Forms.Label legendLabelOnlyInOrg;
+ private System.Windows.Forms.Label legendSwatchOnlyInSecond;
+ private System.Windows.Forms.Label legendLabelOnlyInSecond;
+ private System.Windows.Forms.Label legendSwatchDifferent;
+ private System.Windows.Forms.Label legendLabelDifferent;
+ private System.Windows.Forms.Label legendSwatchIdentical;
+ private System.Windows.Forms.Label legendLabelIdentical;
+ }
+}
diff --git a/UoFiddler.Plugin.Compare/UserControls/CompareAnimDataControl.cs b/UoFiddler.Plugin.Compare/UserControls/CompareAnimDataControl.cs
new file mode 100644
index 00000000..31aff792
--- /dev/null
+++ b/UoFiddler.Plugin.Compare/UserControls/CompareAnimDataControl.cs
@@ -0,0 +1,432 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Windows.Forms;
+using Ultima;
+using UoFiddler.Controls.Classes;
+using UoFiddler.Controls.UserControls.TileView;
+using UoFiddler.Plugin.Compare.Classes;
+
+namespace UoFiddler.Plugin.Compare.UserControls
+{
+ public partial class CompareAnimDataControl : UserControl
+ {
+ public CompareAnimDataControl()
+ {
+ InitializeComponent();
+ }
+
+ private readonly Dictionary _compare = new Dictionary();
+ private readonly List _displayIndices = new List();
+ private bool _syncingSelection;
+
+ private void OnLoad(object sender, EventArgs e)
+ {
+ PopulateOrgList();
+ ControlEvents.FilePathChangeEvent += OnFilePathChangeEvent;
+ }
+
+ private void OnFilePathChangeEvent()
+ {
+ _compare.Clear();
+ PopulateOrgList();
+ }
+
+ private void PopulateOrgList()
+ {
+ _displayIndices.Clear();
+ foreach (int id in Animdata.AnimData.Keys.OrderBy(k => k))
+ {
+ _displayIndices.Add(id);
+ }
+
+ tileViewOrg.VirtualListSize = _displayIndices.Count;
+ tileViewSec.VirtualListSize = 0;
+ }
+
+ private void OnTileViewSizeChanged(object sender, EventArgs e)
+ {
+ var tv = (TileViewControl)sender;
+ int w = tv.DisplayRectangle.Width;
+ if (w > 0 && tv.TileSize.Width != w)
+ {
+ tv.TileSize = new Size(w, tv.TileSize.Height);
+ }
+ }
+
+ private void OnDrawItemOrg(object sender, TileViewControl.DrawTileListItemEventArgs e)
+ {
+ DrawListItem(e, _displayIndices[e.Index]);
+ }
+
+ private void OnDrawItemSec(object sender, TileViewControl.DrawTileListItemEventArgs e)
+ {
+ DrawListItem(e, _displayIndices[e.Index]);
+ }
+
+ private void DrawListItem(DrawItemEventArgs e, int id)
+ {
+ if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
+ {
+ e.Graphics.FillRectangle(Brushes.LightSteelBlue, e.Bounds);
+ }
+ else
+ {
+ e.Graphics.FillRectangle(new SolidBrush(e.BackColor), e.Bounds);
+ }
+
+ Brush fontBrush = GetEntryBrush(id);
+ string text = $"0x{id:X4} ({id})";
+ float y = e.Bounds.Y + (e.Bounds.Height - e.Graphics.MeasureString(text, e.Font).Height) / 2f;
+ e.Graphics.DrawString(text, e.Font, fontBrush, new PointF(4, y));
+ }
+
+ private Brush GetEntryBrush(int id)
+ {
+ bool inOrg = Animdata.AnimData.ContainsKey(id);
+ bool inSec = SecondAnimdata.IsLoaded && SecondAnimdata.GetAnimData(id) != null;
+
+ if (SecondAnimdata.IsLoaded)
+ {
+ if (inOrg && !inSec)
+ {
+ return Brushes.Orange;
+ }
+
+ if (!inOrg && inSec)
+ {
+ return Brushes.Green;
+ }
+
+ if (inOrg && inSec && !Compare(id))
+ {
+ return Brushes.Blue;
+ }
+ }
+
+ return Brushes.Gray;
+ }
+
+ private void OnFocusChangedOrg(object sender, TileViewControl.ListViewFocusedItemSelectionChangedEventArgs e)
+ {
+ if (e.FocusedItemIndex < 0)
+ {
+ return;
+ }
+
+ int id = _displayIndices[e.FocusedItemIndex];
+
+ if (SecondAnimdata.IsLoaded && tileViewSec.VirtualListSize > 0)
+ {
+ if (_syncingSelection)
+ {
+ return;
+ }
+
+ _syncingSelection = true;
+ try
+ {
+ tileViewSec.FocusIndex = e.FocusedItemIndex;
+ }
+ finally
+ {
+ _syncingSelection = false;
+ }
+ }
+
+ UpdateDetailPanel(id);
+ tileViewOrg.Invalidate();
+ }
+
+ private void OnFocusChangedSec(object sender, TileViewControl.ListViewFocusedItemSelectionChangedEventArgs e)
+ {
+ if (e.FocusedItemIndex < 0)
+ {
+ return;
+ }
+
+ int id = _displayIndices[e.FocusedItemIndex];
+
+ if (_syncingSelection)
+ {
+ return;
+ }
+
+ _syncingSelection = true;
+ try
+ {
+ tileViewOrg.FocusIndex = e.FocusedItemIndex;
+ }
+ finally
+ {
+ _syncingSelection = false;
+ }
+
+ UpdateDetailPanel(id);
+ tileViewSec.Invalidate();
+ }
+
+ private void UpdateDetailPanel(int id)
+ {
+ var orgEntry = Animdata.GetAnimData(id);
+ var secEntry = SecondAnimdata.GetAnimData(id);
+
+ if (orgEntry != null)
+ {
+ labelOrgFrameCount.Text = orgEntry.FrameCount.ToString();
+ labelOrgFrameInterval.Text = orgEntry.FrameInterval.ToString();
+ labelOrgFrameStart.Text = orgEntry.FrameStart.ToString();
+ labelOrgFrameData.Text = FormatFrameData(orgEntry.FrameData, orgEntry.FrameCount);
+ }
+ else
+ {
+ labelOrgFrameCount.Text = "-";
+ labelOrgFrameInterval.Text = "-";
+ labelOrgFrameStart.Text = "-";
+ labelOrgFrameData.Text = "-";
+ }
+
+ if (secEntry != null)
+ {
+ labelSecFrameCount.Text = secEntry.FrameCount.ToString();
+ labelSecFrameInterval.Text = secEntry.FrameInterval.ToString();
+ labelSecFrameStart.Text = secEntry.FrameStart.ToString();
+ labelSecFrameData.Text = FormatFrameData(secEntry.FrameData, secEntry.FrameCount);
+ }
+ else
+ {
+ labelSecFrameCount.Text = "-";
+ labelSecFrameInterval.Text = "-";
+ labelSecFrameStart.Text = "-";
+ labelSecFrameData.Text = "-";
+ }
+ }
+
+ private static string FormatFrameData(sbyte[] data, byte count)
+ {
+ if (data == null || count == 0)
+ {
+ return "-";
+ }
+
+ int len = Math.Min(count, data.Length);
+ return string.Join(", ", data.Take(len));
+ }
+
+ private void OnClickBrowse(object sender, EventArgs e)
+ {
+ using (OpenFileDialog dialog = new OpenFileDialog())
+ {
+ dialog.Title = "Select animdata.mul";
+ dialog.Filter = "animdata.mul|animdata.mul|All files (*.*)|*.*";
+ dialog.FileName = "animdata.mul";
+ if (dialog.ShowDialog() == DialogResult.OK)
+ {
+ textBoxSecondFile.Text = dialog.FileName;
+ }
+ }
+ }
+
+ private void OnClickLoadSecond(object sender, EventArgs e)
+ {
+ string path = textBoxSecondFile.Text?.Trim();
+ if (string.IsNullOrEmpty(path))
+ {
+ return;
+ }
+
+ if (!SecondAnimdata.Initialize(path))
+ {
+ MessageBox.Show("Failed to load the selected animdata.mul file.", "Error",
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ return;
+ }
+
+ _compare.Clear();
+ RefreshLists();
+ }
+
+ private void RefreshLists()
+ {
+ var allIds = Animdata.AnimData.Keys
+ .Union(SecondAnimdata.GetKeys())
+ .OrderBy(k => k)
+ .ToList();
+
+ _displayIndices.Clear();
+ foreach (int id in allIds)
+ {
+ _displayIndices.Add(id);
+ }
+
+ tileViewOrg.VirtualListSize = _displayIndices.Count;
+ tileViewSec.VirtualListSize = _displayIndices.Count;
+ tileViewOrg.Invalidate();
+ tileViewSec.Invalidate();
+ }
+
+ private void OnChangeShowDiff(object sender, EventArgs e)
+ {
+ if (!SecondAnimdata.IsLoaded)
+ {
+ if (checkBoxShowDiff.Checked)
+ {
+ MessageBox.Show("Second AnimData file is not loaded.", "Info",
+ MessageBoxButtons.OK, MessageBoxIcon.Information);
+ checkBoxShowDiff.Checked = false;
+ }
+ return;
+ }
+
+ Cursor.Current = Cursors.WaitCursor;
+ var allIds = Animdata.AnimData.Keys
+ .Union(SecondAnimdata.GetKeys())
+ .OrderBy(k => k);
+
+ _displayIndices.Clear();
+ foreach (int id in allIds)
+ {
+ if (!checkBoxShowDiff.Checked || !Compare(id))
+ {
+ _displayIndices.Add(id);
+ }
+ }
+
+ tileViewOrg.VirtualListSize = _displayIndices.Count;
+ tileViewSec.VirtualListSize = _displayIndices.Count;
+ Cursor.Current = Cursors.Default;
+ }
+
+ private bool Compare(int id)
+ {
+ if (_compare.TryGetValue(id, out bool cached))
+ {
+ return cached;
+ }
+
+ var e1 = Animdata.GetAnimData(id);
+ var e2 = SecondAnimdata.GetAnimData(id);
+
+ if (e1 == null && e2 == null)
+ {
+ _compare[id] = true;
+ return true;
+ }
+
+ if (e1 == null || e2 == null)
+ {
+ _compare[id] = false;
+ return false;
+ }
+
+ bool same = e1.FrameCount == e2.FrameCount
+ && e1.FrameInterval == e2.FrameInterval
+ && e1.FrameStart == e2.FrameStart
+ && e1.FrameData.SequenceEqual(e2.FrameData);
+
+ _compare[id] = same;
+ return same;
+ }
+
+ private void OnDoubleClickSec(object sender, MouseEventArgs e)
+ {
+ OnClickCopySelected(sender, e);
+ }
+
+ private void OnClickCopySelected(object sender, EventArgs e)
+ {
+ int focusIdx = tileViewSec.FocusIndex;
+ if (focusIdx < 0)
+ {
+ return;
+ }
+
+ int id = _displayIndices[focusIdx];
+ CopyEntry(id);
+
+ if (checkBoxShowDiff.Checked)
+ {
+ _displayIndices.RemoveAt(focusIdx);
+ tileViewOrg.VirtualListSize = _displayIndices.Count;
+ tileViewSec.VirtualListSize = _displayIndices.Count;
+ }
+
+ tileViewOrg.Invalidate();
+ tileViewSec.Invalidate();
+ UpdateDetailPanel(id);
+ }
+
+ private void OnClickCopyAllDiff(object sender, EventArgs e)
+ {
+ if (!SecondAnimdata.IsLoaded)
+ {
+ return;
+ }
+
+ bool changed = false;
+ var allIds = Animdata.AnimData.Keys.Union(SecondAnimdata.GetKeys()).ToList();
+ foreach (int id in allIds)
+ {
+ if (!Compare(id) && SecondAnimdata.GetAnimData(id) != null)
+ {
+ CopyEntry(id);
+ changed = true;
+ }
+ }
+
+ if (changed)
+ {
+ if (checkBoxShowDiff.Checked)
+ {
+ OnChangeShowDiff(sender, e);
+ }
+
+ tileViewOrg.Invalidate();
+ tileViewSec.Invalidate();
+ }
+ }
+
+ private void OnClickCopyAddedOnly(object sender, EventArgs e)
+ {
+ if (!SecondAnimdata.IsLoaded)
+ {
+ return;
+ }
+
+ bool changed = false;
+ foreach (int id in SecondAnimdata.GetKeys())
+ {
+ if (!Animdata.AnimData.ContainsKey(id))
+ {
+ CopyEntry(id);
+ changed = true;
+ }
+ }
+
+ if (changed)
+ {
+ RefreshLists();
+ }
+ }
+
+ private void CopyEntry(int id)
+ {
+ var src = SecondAnimdata.GetAnimData(id);
+ if (src == null)
+ {
+ return;
+ }
+
+ Animdata.AnimData[id] = new Animdata.AnimdataEntry(
+ (sbyte[])src.FrameData.Clone(),
+ src.Unknown,
+ src.FrameCount,
+ src.FrameInterval,
+ src.FrameStart);
+
+ Options.ChangedUltimaClass["Animdata"] = true;
+ _compare[id] = true;
+ }
+ }
+}
diff --git a/UoFiddler.Plugin.Compare/UserControls/CompareAnimDataControl.resx b/UoFiddler.Plugin.Compare/UserControls/CompareAnimDataControl.resx
new file mode 100644
index 00000000..68d1a995
--- /dev/null
+++ b/UoFiddler.Plugin.Compare/UserControls/CompareAnimDataControl.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+ 17, 17
+
+
\ No newline at end of file
diff --git a/UoFiddler.Plugin.Compare/UserControls/CompareGumpControl.Designer.cs b/UoFiddler.Plugin.Compare/UserControls/CompareGumpControl.Designer.cs
index 2e014f7b..e6c4671a 100644
--- a/UoFiddler.Plugin.Compare/UserControls/CompareGumpControl.Designer.cs
+++ b/UoFiddler.Plugin.Compare/UserControls/CompareGumpControl.Designer.cs
@@ -40,8 +40,8 @@ protected override void Dispose(bool disposing)
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
- this.listBox1 = new System.Windows.Forms.ListBox();
- this.listBox2 = new System.Windows.Forms.ListBox();
+ this.tileView1 = new UoFiddler.Controls.UserControls.TileView.TileViewControl();
+ this.tileView2 = new UoFiddler.Controls.UserControls.TileView.TileViewControl();
this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);
this.extractAsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.tiffToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@@ -64,41 +64,41 @@ private void InitializeComponent()
this.splitContainer1.Panel2.SuspendLayout();
this.splitContainer1.SuspendLayout();
this.SuspendLayout();
- //
- // listBox1
- //
- this.listBox1.Dock = System.Windows.Forms.DockStyle.Left;
- this.listBox1.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
- this.listBox1.FormattingEnabled = true;
- this.listBox1.IntegralHeight = false;
- this.listBox1.ItemHeight = 60;
- this.listBox1.Location = new System.Drawing.Point(0, 0);
- this.listBox1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
- this.listBox1.Name = "listBox1";
- this.listBox1.Size = new System.Drawing.Size(174, 320);
- this.listBox1.TabIndex = 0;
- this.listBox1.Tag = 1;
- this.listBox1.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.Listbox1_DrawItem);
- this.listBox1.MeasureItem += new System.Windows.Forms.MeasureItemEventHandler(this.Listbox_measureItem);
- this.listBox1.SelectedIndexChanged += new System.EventHandler(this.Listbox_SelectedChange);
- //
- // listBox2
- //
- this.listBox2.ContextMenuStrip = this.contextMenuStrip1;
- this.listBox2.Dock = System.Windows.Forms.DockStyle.Right;
- this.listBox2.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
- this.listBox2.FormattingEnabled = true;
- this.listBox2.IntegralHeight = false;
- this.listBox2.ItemHeight = 60;
- this.listBox2.Location = new System.Drawing.Point(556, 0);
- this.listBox2.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
- this.listBox2.Name = "listBox2";
- this.listBox2.Size = new System.Drawing.Size(174, 320);
- this.listBox2.TabIndex = 1;
- this.listBox2.Tag = 2;
- this.listBox2.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.Listbox1_DrawItem);
- this.listBox2.MeasureItem += new System.Windows.Forms.MeasureItemEventHandler(this.Listbox_measureItem);
- this.listBox2.SelectedIndexChanged += new System.EventHandler(this.Listbox_SelectedChange);
+ //
+ // tileView1
+ //
+ this.tileView1.Dock = System.Windows.Forms.DockStyle.Left;
+ this.tileView1.Location = new System.Drawing.Point(0, 0);
+ this.tileView1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ this.tileView1.Name = "tileView1";
+ this.tileView1.Size = new System.Drawing.Size(174, 320);
+ this.tileView1.TabIndex = 0;
+ this.tileView1.TileSize = new System.Drawing.Size(174, 60);
+ this.tileView1.TileMargin = new System.Windows.Forms.Padding(0);
+ this.tileView1.TilePadding = new System.Windows.Forms.Padding(0);
+ this.tileView1.TileBorderWidth = 0f;
+ this.tileView1.TileHighLightOpacity = 0.0;
+ this.tileView1.DrawItem += new System.EventHandler(this.OnDrawItem1);
+ this.tileView1.FocusSelectionChanged += new System.EventHandler(this.OnFocusChanged1);
+ this.tileView1.SizeChanged += new System.EventHandler(this.OnTileViewSizeChanged);
+ //
+ // tileView2
+ //
+ this.tileView2.ContextMenuStrip = this.contextMenuStrip1;
+ this.tileView2.Dock = System.Windows.Forms.DockStyle.Right;
+ this.tileView2.Location = new System.Drawing.Point(556, 0);
+ this.tileView2.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ this.tileView2.Name = "tileView2";
+ this.tileView2.Size = new System.Drawing.Size(174, 320);
+ this.tileView2.TabIndex = 1;
+ this.tileView2.TileSize = new System.Drawing.Size(174, 60);
+ this.tileView2.TileMargin = new System.Windows.Forms.Padding(0);
+ this.tileView2.TilePadding = new System.Windows.Forms.Padding(0);
+ this.tileView2.TileBorderWidth = 0f;
+ this.tileView2.TileHighLightOpacity = 0.0;
+ this.tileView2.DrawItem += new System.EventHandler(this.OnDrawItem2);
+ this.tileView2.FocusSelectionChanged += new System.EventHandler(this.OnFocusChanged2);
+ this.tileView2.SizeChanged += new System.EventHandler(this.OnTileViewSizeChanged);
//
// contextMenuStrip1
//
@@ -188,8 +188,8 @@ private void InitializeComponent()
// splitContainer1.Panel1
//
this.splitContainer1.Panel1.Controls.Add(this.tableLayoutPanel1);
- this.splitContainer1.Panel1.Controls.Add(this.listBox2);
- this.splitContainer1.Panel1.Controls.Add(this.listBox1);
+ this.splitContainer1.Panel1.Controls.Add(this.tileView2);
+ this.splitContainer1.Panel1.Controls.Add(this.tileView1);
//
// splitContainer1.Panel2
//
@@ -278,8 +278,8 @@ private void InitializeComponent()
private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;
private System.Windows.Forms.ToolStripMenuItem copyGump2To1ToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem extractAsToolStripMenuItem;
- private System.Windows.Forms.ListBox listBox1;
- private System.Windows.Forms.ListBox listBox2;
+ private UoFiddler.Controls.UserControls.TileView.TileViewControl tileView1;
+ private UoFiddler.Controls.UserControls.TileView.TileViewControl tileView2;
private System.Windows.Forms.PictureBox pictureBox1;
private System.Windows.Forms.PictureBox pictureBox2;
private System.Windows.Forms.SplitContainer splitContainer1;
diff --git a/UoFiddler.Plugin.Compare/UserControls/CompareGumpControl.cs b/UoFiddler.Plugin.Compare/UserControls/CompareGumpControl.cs
index a9a550bf..ec66eeac 100644
--- a/UoFiddler.Plugin.Compare/UserControls/CompareGumpControl.cs
+++ b/UoFiddler.Plugin.Compare/UserControls/CompareGumpControl.cs
@@ -1,9 +1,9 @@
-/***************************************************************************
+/***************************************************************************
*
* $Author: Turley
- *
+ *
* "THE BEER-WARE LICENSE"
- * As long as you retain this notice you can do whatever you want with
+ * 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.
*
@@ -18,6 +18,7 @@
using System.Windows.Forms;
using Ultima;
using UoFiddler.Controls.Classes;
+using UoFiddler.Controls.UserControls.TileView;
using UoFiddler.Plugin.Compare.Classes;
namespace UoFiddler.Plugin.Compare.UserControls
@@ -27,12 +28,12 @@ public partial class CompareGumpControl : UserControl
public CompareGumpControl()
{
InitializeComponent();
- SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
}
private readonly Dictionary _compare = new Dictionary();
private readonly SHA256 _sha256 = SHA256.Create();
-
+ private readonly List _displayIndices = new List();
+ private bool _syncingSelection;
private bool _loaded;
private void OnLoad(object sender, EventArgs e)
@@ -40,19 +41,18 @@ private void OnLoad(object sender, EventArgs e)
Cursor.Current = Cursors.WaitCursor;
Options.LoadedUltimaClass["Gumps"] = true;
- listBox1.BeginUpdate();
- listBox1.Items.Clear();
- List