diff --git a/Distribution/Data/Binary/Bounds.bin b/Distribution/Data/Binary/Bounds.bin deleted file mode 100644 index 11431362e..000000000 Binary files a/Distribution/Data/Binary/Bounds.bin and /dev/null differ diff --git a/Distribution/Data/Items/ItemBounds.bin b/Distribution/Data/Items/ItemBounds.bin new file mode 100644 index 000000000..1e0214cbf Binary files /dev/null and b/Distribution/Data/Items/ItemBounds.bin differ diff --git a/Projects/Server/Client/ArtData.cs b/Projects/Server/Client/ArtData.cs index ba6e4167a..d8f54d868 100644 --- a/Projects/Server/Client/ArtData.cs +++ b/Projects/Server/Client/ArtData.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2024 - ModernUO Development Team * + * Copyright 2019-2025 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ArtData.cs * * * @@ -50,18 +50,18 @@ public class ArtData : IDisposable } } - public Rectangle2D GetStaticBounds(int index) + public (ushort Width, ushort Height, Rectangle2D Bounds) GetStaticBounds(int index) { if (index is < 0 or > 0x10000) { - return Rectangle2D.Empty; + return (0, 0, Rectangle2D.Empty); } index += 16384; if (!_dataRanges.TryGetValue(index, out var entry)) { - return Rectangle2D.Empty; + return (0, 0, Rectangle2D.Empty); } Span buffer = stackalloc ushort[entry.Size / 2]; @@ -73,10 +73,10 @@ public class ArtData : IDisposable if (width == 0 || height == 0) { - return Rectangle2D.Empty; + return (0, 0, Rectangle2D.Empty); } - return GetBoundsFromRGBA1555Bitmap(width, height, buffer[4..]); + return (width, height, GetBoundsFromRGBA1555Bitmap(width, height, buffer[4..])); } private static Dictionary LoadMulRanges(string idxPath) diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index 2a17627ff..0fd0dc0bd 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -2346,7 +2346,7 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt }; } - var bounds = ItemBounds.Table[itemID & 0x3FFF]; + var bounds = ItemBounds.Bounds[itemID & 0x3FFF]; if (doubled) { diff --git a/Projects/Server/Items/ItemBounds.cs b/Projects/Server/Items/ItemBounds.cs index 4b957b80e..5f94eeca8 100644 --- a/Projects/Server/Items/ItemBounds.cs +++ b/Projects/Server/Items/ItemBounds.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * + * Copyright 2019-2025 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ItemBounds.cs * * * @@ -17,13 +17,15 @@ using System; using System.IO; using System.Threading; using Server.Logging; +using Size = System.ValueTuple; namespace Server; public static class ItemBounds { private static readonly ILogger logger = LogFactory.GetLogger(typeof(ItemBounds)); - private static readonly string _pathToBounds = Path.Combine(Core.BaseDirectory, "Data", "Binary", "Bounds.bin"); + private const string _boundsFileName = "ItemBounds.bin"; + private static readonly string _boundsFolder = Path.Combine(Core.BaseDirectory, "Data", "Items"); private static bool _isGenerating; public static void Configure() @@ -32,7 +34,7 @@ public static class ItemBounds } [Usage("GenBounds")] - [Description("Asynchronously generates the bounds.bin file from art.mul/artidx.mul or artLegacyMUL.uop to determine container boundaries.")] + [Description("Asynchronously generates the ItemBounds.bin file from art.mul/artidx.mul or artLegacyMUL.uop to determine graphic sizes and boundaries.")] private static void GenBounds_OnCommand(CommandEventArgs e) { GenerateBoundsFileAsync(e.Mobile); @@ -40,19 +42,20 @@ public static class ItemBounds static ItemBounds() { - Table = new Rectangle2D[TileData.ItemTable.Length]; - - if (!File.Exists(_pathToBounds)) + if (!File.Exists(Path.Combine(_boundsFolder, _boundsFileName))) { - logger.Information("Generating {BoundsFilePath}...", "Bounds.bin"); + logger.Information("Generating {BoundsFilePath}...", _boundsFileName); try { - GenerateBoundsFile(); - logger.Information("Generated {BoundsFilePath} successfully.", "Bounds.bin"); + GenerateBoundsFile(out var sizes, out var bounds); + Sizes = sizes; + Bounds = bounds; + + logger.Information("Generated {BoundsFilePath} successfully.", _boundsFileName); } catch (Exception ex) { - logger.Error(ex, "Failed to generate {BoundsFilePath}", "Bounds.bin"); + logger.Error(ex, "Failed to generate {BoundsFilePath}", _boundsFileName); } return; @@ -61,7 +64,9 @@ public static class ItemBounds GenerateTable(); } - public static Rectangle2D[] Table { get; private set; } + public static Size[] Sizes { get; private set; } + + public static Rectangle2D[] Bounds { get; private set; } private static void GenerateBoundsFileAsync(Mobile m) { @@ -76,16 +81,21 @@ public static class ItemBounds state => { var from = state as Mobile; - logger.Information("Generating {BoundsFilePath}...", "Bounds.bin"); + logger.Information("Generating {BoundsFilePath}...", _boundsFileName); if (from != null) { - Core.LoopContext.Post(() => from?.SendMessage("Generating bounds file...")); + Core.LoopContext.Post(() => from.SendMessage("Generating bounds file...")); } try { - var table = GenerateBoundsFile(); - Core.LoopContext.Post(() => Table = table); + GenerateBoundsFile(out var sizes, out var bounds); + Core.LoopContext.Post(() => + { + Sizes = sizes; + Bounds = bounds; + } + ); } catch (Exception ex) { @@ -93,21 +103,21 @@ public static class ItemBounds { Core.LoopContext.Post(() => { - from?.SendMessage("Failed to generate bounds file:"); - from?.SendMessage(ex.Message); + from.SendMessage("Failed to generate bounds file:"); + from.SendMessage(ex.Message); } ); } - logger.Error(ex, "Failed to generate {BoundsFilePath}", "Bounds.bin"); + logger.Error(ex, "Failed to generate {BoundsFilePath}", _boundsFileName); return; } if (from != null) { Core.LoopContext.Post( - () => from?.SendMessage( - $"Bounds file saved to {Path.GetRelativePath(Core.BaseDirectory, _pathToBounds)}." + () => from.SendMessage( + $"Bounds file saved to {Path.GetRelativePath(Core.BaseDirectory, Path.Combine(_boundsFolder, _boundsFileName))}." ) ); } @@ -119,48 +129,57 @@ public static class ItemBounds ); } - private static Rectangle2D[] GenerateBoundsFile() + private static void GenerateBoundsFile(out Size[] sizes, out Rectangle2D[] bounds) { - var table = new Rectangle2D[TileData.ItemTable.Length]; + bounds = new Rectangle2D[TileData.ItemTable.Length]; + sizes = new Size[TileData.ItemTable.Length]; + using var artData = new ArtData(); if (!artData.IsInitialized) { throw new FileNotFoundException("Unable to load art.mul/artidx.mul or artLegacyMUL.uop"); } - using var fs = new FileStream(_pathToBounds, FileMode.Create, FileAccess.Write); + PathUtility.EnsureDirectory(_boundsFolder); + using var fs = new FileStream(Path.Combine(_boundsFolder, _boundsFileName), FileMode.Create, FileAccess.Write); using var bw = new BinaryWriter(fs); - for (var i = 0; i < table.Length; i++) + for (var i = 0; i < bounds.Length; i++) { - var bounds = artData.GetStaticBounds(i); + var (w, h, b) = artData.GetStaticBounds(i); - bw.Write((short)bounds.X); - bw.Write((short)bounds.Y); - bw.Write((short)(bounds.X + bounds.Width + 1)); - bw.Write((short)(bounds.Y + bounds.Height + 1)); + bw.Write(w); + bw.Write(h); + bw.Write((short)b.X); + bw.Write((short)b.Y); + bw.Write((short)(b.X + b.Width + 1)); + bw.Write((short)(b.Y + b.Height + 1)); - table[i] = bounds; + bounds[i].Set(b.X, b.Y, b.Width, b.Height); + sizes[i] = (w, h); } - - return table; } private static void GenerateTable() { - using var fs = new FileStream(_pathToBounds, FileMode.Open, FileAccess.Read, FileShare.Read); + Bounds = new Rectangle2D[TileData.ItemTable.Length]; + Sizes = new Size[TileData.ItemTable.Length]; + + using var fs = new FileStream(Path.Combine(_boundsFolder, _boundsFileName), FileMode.Open, FileAccess.Read, FileShare.Read); using var bin = new BinaryReader(fs); - var count = Math.Min(Table.Length, (int)(fs.Length / 8)); + var count = Math.Min(Bounds.Length, (int)(fs.Length / 8)); for (var i = 0; i < count; ++i) { + Sizes[i] = (bin.ReadUInt16(), bin.ReadUInt16()); + int xMin = bin.ReadInt16(); int yMin = bin.ReadInt16(); int xMax = bin.ReadInt16(); int yMax = bin.ReadInt16(); - Table[i].Set(xMin, yMin, xMax - xMin, yMax - yMin); + Bounds[i].Set(xMin, yMin, xMax - xMin, yMax - yMin); } } } diff --git a/Projects/UOContent/Commands/Object Creation/CategorizedAddGump.cs b/Projects/UOContent/Commands/Object Creation/CategorizedAddGump.cs index b46812115..d8ea63234 100644 --- a/Projects/UOContent/Commands/Object Creation/CategorizedAddGump.cs +++ b/Projects/UOContent/Commands/Object Creation/CategorizedAddGump.cs @@ -187,7 +187,7 @@ namespace Server.Gumps Console.WriteLine("Type {0} does not have a valid item id or shrink table entry.", obj.Type); } - var bounds = ItemBounds.Table[itemID]; + var bounds = ItemBounds.Bounds[itemID]; if (itemID != 1 && bounds.Height < EntryHeight * 2) { diff --git a/Projects/UOContent/Engines/Factions/Gumps/SheriffGump.cs b/Projects/UOContent/Engines/Factions/Gumps/SheriffGump.cs index f5a466259..112b6dc4b 100644 --- a/Projects/UOContent/Engines/Factions/Gumps/SheriffGump.cs +++ b/Projects/UOContent/Engines/Factions/Gumps/SheriffGump.cs @@ -118,7 +118,7 @@ public class SheriffGump : FactionGump private void CenterItem(int itemID, int x, int y, int w, int h) { - var rc = ItemBounds.Table[itemID]; + var rc = ItemBounds.Bounds[itemID]; AddItem(x + (w - rc.Width) / 2 - rc.X, y + (h - rc.Height) / 2 - rc.Y, itemID); } diff --git a/Projects/UOContent/Gumps/Base/BaseGump.cs b/Projects/UOContent/Gumps/Base/BaseGump.cs index a3b393b7a..2682ce872 100644 --- a/Projects/UOContent/Gumps/Base/BaseGump.cs +++ b/Projects/UOContent/Gumps/Base/BaseGump.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2024 - ModernUO Development Team * + * Copyright 2019-2025 - ModernUO Development Team * * Email: hi@modernuo.com * * File: BaseGump.cs * * * @@ -86,4 +86,31 @@ public abstract class BaseGump return hash == 461 ? hash * primeMulti : hash; } } + + public static Point2D GetItemGraphicOffset(int itemId) + { + var (width, height) = ItemBounds.Sizes[itemId]; + var x = 0; + var y = 0; + + if (width > 44) + { + x -= (width - 44) / 2; + } + else if (width < 44) + { + x += (44 - width) / 2; + } + + if (height > 44) + { + y -= height - 44; + } + else if (height < 44) + { + y += 44 - height; + } + + return new Point2D(x, y); + } } diff --git a/Projects/UOContent/Gumps/RewardGump.cs b/Projects/UOContent/Gumps/RewardGump.cs index 9d4709fba..53a3f8038 100644 --- a/Projects/UOContent/Gumps/RewardGump.cs +++ b/Projects/UOContent/Gumps/RewardGump.cs @@ -62,7 +62,7 @@ namespace Server.Gumps { var entry = Rewards[i]; - var bounds = ItemBounds.Table[entry.ItemID]; + var bounds = ItemBounds.Bounds[entry.ItemID]; var height = Math.Max(36, bounds.Height); if (offset + height > 320) diff --git a/Projects/UOContent/Items/Special/Mutation Core/PlagueBeastBackpack.cs b/Projects/UOContent/Items/Special/Mutation Core/PlagueBeastBackpack.cs index f9681165c..5aeeefa2c 100644 --- a/Projects/UOContent/Items/Special/Mutation Core/PlagueBeastBackpack.cs +++ b/Projects/UOContent/Items/Special/Mutation Core/PlagueBeastBackpack.cs @@ -125,7 +125,7 @@ public partial class PlagueBeastBackpack : BaseContainer return false; } - var ir = ItemBounds.Table[item.ItemID]; + var ir = ItemBounds.Bounds[item.ItemID]; int x, y; var cx = p.X + ir.X + ir.Width / 2; var cy = p.Y + ir.Y + ir.Height / 2; @@ -134,7 +134,7 @@ public partial class PlagueBeastBackpack : BaseContainer { if (Items[i] is PlagueBeastComponent innard) { - var r = ItemBounds.Table[innard.ItemID]; + var r = ItemBounds.Bounds[innard.ItemID]; x = innard.X + r.X; y = innard.Y + r.Y; diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index 9da05f1c1..54c9fe4b0 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -4733,7 +4733,7 @@ namespace Server.Mobiles for (int i = _page * 4, y = 72; i < (_page + 1) * 4 && i < _items.Length; ++i, y += 75) { var item = _items[i]; - var b = ItemBounds.Table[item.ItemID]; + var b = ItemBounds.Bounds[item.ItemID]; builder.AddImageTiledButton( 40, diff --git a/Projects/UOContent/Spells/Ninjitsu/AnimalForm.cs b/Projects/UOContent/Spells/Ninjitsu/AnimalForm.cs index abe0847d9..d65d3b135 100644 --- a/Projects/UOContent/Spells/Ninjitsu/AnimalForm.cs +++ b/Projects/UOContent/Spells/Ninjitsu/AnimalForm.cs @@ -422,7 +422,7 @@ public class AnimalForm : NinjaSpell int y = Math.DivRem(pos, 2, out var rem) * 64 + 44; int x = rem == 0 ? 14 : 264; - Rectangle2D b = ItemBounds.Table[entry.ItemID]; + Rectangle2D b = ItemBounds.Bounds[entry.ItemID]; builder.AddImageTiledButton(x, y, 0x918, 0x919, i + 1, GumpButtonType.Reply, 0, entry.ItemID, entry.Hue, 40 - b.Width / 2 - b.X, 30 - b.Height / 2 - b.Y, entry.Tooltip);