feat: Adds item graphic size to Bounds.bin and makes item graphic offset available to gumps (#2115)

This commit is contained in:
Kamron Batman 2025-02-10 19:31:39 -08:00 committed by GitHub
parent 772511d1e8
commit 40479c946a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 96 additions and 50 deletions

Binary file not shown.

Binary file not shown.

View file

@ -1,6 +1,6 @@
/************************************************************************* /*************************************************************************
* ModernUO * * ModernUO *
* Copyright 2019-2024 - ModernUO Development Team * * Copyright 2019-2025 - ModernUO Development Team *
* Email: hi@modernuo.com * * Email: hi@modernuo.com *
* File: ArtData.cs * * 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) if (index is < 0 or > 0x10000)
{ {
return Rectangle2D.Empty; return (0, 0, Rectangle2D.Empty);
} }
index += 16384; index += 16384;
if (!_dataRanges.TryGetValue(index, out var entry)) if (!_dataRanges.TryGetValue(index, out var entry))
{ {
return Rectangle2D.Empty; return (0, 0, Rectangle2D.Empty);
} }
Span<ushort> buffer = stackalloc ushort[entry.Size / 2]; Span<ushort> buffer = stackalloc ushort[entry.Size / 2];
@ -73,10 +73,10 @@ public class ArtData : IDisposable
if (width == 0 || height == 0) 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<int, UOPEntry> LoadMulRanges(string idxPath) private static Dictionary<int, UOPEntry> LoadMulRanges(string idxPath)

View file

@ -2346,7 +2346,7 @@ public class Item : IHued, IComparable<Item>, ISpawnable, IObjectPropertyListEnt
}; };
} }
var bounds = ItemBounds.Table[itemID & 0x3FFF]; var bounds = ItemBounds.Bounds[itemID & 0x3FFF];
if (doubled) if (doubled)
{ {

View file

@ -1,6 +1,6 @@
/************************************************************************* /*************************************************************************
* ModernUO * * ModernUO *
* Copyright 2019-2023 - ModernUO Development Team * * Copyright 2019-2025 - ModernUO Development Team *
* Email: hi@modernuo.com * * Email: hi@modernuo.com *
* File: ItemBounds.cs * * File: ItemBounds.cs *
* * * *
@ -17,13 +17,15 @@ using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using Server.Logging; using Server.Logging;
using Size = System.ValueTuple<ushort, ushort>;
namespace Server; namespace Server;
public static class ItemBounds public static class ItemBounds
{ {
private static readonly ILogger logger = LogFactory.GetLogger(typeof(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; private static bool _isGenerating;
public static void Configure() public static void Configure()
@ -32,7 +34,7 @@ public static class ItemBounds
} }
[Usage("GenBounds")] [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) private static void GenBounds_OnCommand(CommandEventArgs e)
{ {
GenerateBoundsFileAsync(e.Mobile); GenerateBoundsFileAsync(e.Mobile);
@ -40,19 +42,20 @@ public static class ItemBounds
static ItemBounds() static ItemBounds()
{ {
Table = new Rectangle2D[TileData.ItemTable.Length]; if (!File.Exists(Path.Combine(_boundsFolder, _boundsFileName)))
if (!File.Exists(_pathToBounds))
{ {
logger.Information("Generating {BoundsFilePath}...", "Bounds.bin"); logger.Information("Generating {BoundsFilePath}...", _boundsFileName);
try try
{ {
GenerateBoundsFile(); GenerateBoundsFile(out var sizes, out var bounds);
logger.Information("Generated {BoundsFilePath} successfully.", "Bounds.bin"); Sizes = sizes;
Bounds = bounds;
logger.Information("Generated {BoundsFilePath} successfully.", _boundsFileName);
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.Error(ex, "Failed to generate {BoundsFilePath}", "Bounds.bin"); logger.Error(ex, "Failed to generate {BoundsFilePath}", _boundsFileName);
} }
return; return;
@ -61,7 +64,9 @@ public static class ItemBounds
GenerateTable(); 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) private static void GenerateBoundsFileAsync(Mobile m)
{ {
@ -76,16 +81,21 @@ public static class ItemBounds
state => state =>
{ {
var from = state as Mobile; var from = state as Mobile;
logger.Information("Generating {BoundsFilePath}...", "Bounds.bin"); logger.Information("Generating {BoundsFilePath}...", _boundsFileName);
if (from != null) if (from != null)
{ {
Core.LoopContext.Post(() => from?.SendMessage("Generating bounds file...")); Core.LoopContext.Post(() => from.SendMessage("Generating bounds file..."));
} }
try try
{ {
var table = GenerateBoundsFile(); GenerateBoundsFile(out var sizes, out var bounds);
Core.LoopContext.Post(() => Table = table); Core.LoopContext.Post(() =>
{
Sizes = sizes;
Bounds = bounds;
}
);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -93,21 +103,21 @@ public static class ItemBounds
{ {
Core.LoopContext.Post(() => Core.LoopContext.Post(() =>
{ {
from?.SendMessage("Failed to generate bounds file:"); from.SendMessage("Failed to generate bounds file:");
from?.SendMessage(ex.Message); from.SendMessage(ex.Message);
} }
); );
} }
logger.Error(ex, "Failed to generate {BoundsFilePath}", "Bounds.bin"); logger.Error(ex, "Failed to generate {BoundsFilePath}", _boundsFileName);
return; return;
} }
if (from != null) if (from != null)
{ {
Core.LoopContext.Post( Core.LoopContext.Post(
() => from?.SendMessage( () => from.SendMessage(
$"Bounds file saved to {Path.GetRelativePath(Core.BaseDirectory, _pathToBounds)}." $"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(); using var artData = new ArtData();
if (!artData.IsInitialized) if (!artData.IsInitialized)
{ {
throw new FileNotFoundException("Unable to load art.mul/artidx.mul or artLegacyMUL.uop"); 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); 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(w);
bw.Write((short)bounds.Y); bw.Write(h);
bw.Write((short)(bounds.X + bounds.Width + 1)); bw.Write((short)b.X);
bw.Write((short)(bounds.Y + bounds.Height + 1)); 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() 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); 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) for (var i = 0; i < count; ++i)
{ {
Sizes[i] = (bin.ReadUInt16(), bin.ReadUInt16());
int xMin = bin.ReadInt16(); int xMin = bin.ReadInt16();
int yMin = bin.ReadInt16(); int yMin = bin.ReadInt16();
int xMax = bin.ReadInt16(); int xMax = bin.ReadInt16();
int yMax = bin.ReadInt16(); int yMax = bin.ReadInt16();
Table[i].Set(xMin, yMin, xMax - xMin, yMax - yMin); Bounds[i].Set(xMin, yMin, xMax - xMin, yMax - yMin);
} }
} }
} }

View file

@ -187,7 +187,7 @@ namespace Server.Gumps
Console.WriteLine("Type {0} does not have a valid item id or shrink table entry.", obj.Type); 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) if (itemID != 1 && bounds.Height < EntryHeight * 2)
{ {

View file

@ -118,7 +118,7 @@ public class SheriffGump : FactionGump
private void CenterItem(int itemID, int x, int y, int w, int h) 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); AddItem(x + (w - rc.Width) / 2 - rc.X, y + (h - rc.Height) / 2 - rc.Y, itemID);
} }

View file

@ -1,6 +1,6 @@
/************************************************************************* /*************************************************************************
* ModernUO * * ModernUO *
* Copyright 2019-2024 - ModernUO Development Team * * Copyright 2019-2025 - ModernUO Development Team *
* Email: hi@modernuo.com * * Email: hi@modernuo.com *
* File: BaseGump.cs * * File: BaseGump.cs *
* * * *
@ -86,4 +86,31 @@ public abstract class BaseGump
return hash == 461 ? hash * primeMulti : hash; 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);
}
} }

View file

@ -62,7 +62,7 @@ namespace Server.Gumps
{ {
var entry = Rewards[i]; var entry = Rewards[i];
var bounds = ItemBounds.Table[entry.ItemID]; var bounds = ItemBounds.Bounds[entry.ItemID];
var height = Math.Max(36, bounds.Height); var height = Math.Max(36, bounds.Height);
if (offset + height > 320) if (offset + height > 320)

View file

@ -125,7 +125,7 @@ public partial class PlagueBeastBackpack : BaseContainer
return false; return false;
} }
var ir = ItemBounds.Table[item.ItemID]; var ir = ItemBounds.Bounds[item.ItemID];
int x, y; int x, y;
var cx = p.X + ir.X + ir.Width / 2; var cx = p.X + ir.X + ir.Width / 2;
var cy = p.Y + ir.Y + ir.Height / 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) if (Items[i] is PlagueBeastComponent innard)
{ {
var r = ItemBounds.Table[innard.ItemID]; var r = ItemBounds.Bounds[innard.ItemID];
x = innard.X + r.X; x = innard.X + r.X;
y = innard.Y + r.Y; y = innard.Y + r.Y;

View file

@ -4733,7 +4733,7 @@ namespace Server.Mobiles
for (int i = _page * 4, y = 72; i < (_page + 1) * 4 && i < _items.Length; ++i, y += 75) for (int i = _page * 4, y = 72; i < (_page + 1) * 4 && i < _items.Length; ++i, y += 75)
{ {
var item = _items[i]; var item = _items[i];
var b = ItemBounds.Table[item.ItemID]; var b = ItemBounds.Bounds[item.ItemID];
builder.AddImageTiledButton( builder.AddImageTiledButton(
40, 40,

View file

@ -422,7 +422,7 @@ public class AnimalForm : NinjaSpell
int y = Math.DivRem(pos, 2, out var rem) * 64 + 44; int y = Math.DivRem(pos, 2, out var rem) * 64 + 44;
int x = rem == 0 ? 14 : 264; 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, 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); entry.Hue, 40 - b.Width / 2 - b.X, 30 - b.Height / 2 - b.Y, entry.Tooltip);