Voxpire

Vita-Nex
Administrator
Apparently so!
ai.imgur.com_RfsrlE3.png
ai.imgur.com_Rwq0819.jpg

Issue: Requires UltimaSDK or OpenUO

So, now that we know it is possible, I will try to port some UOSDK/OpenUO code to allow this to be implemented without those respective assemblies and with any luck, be able to release a stand-alone version as well as including it in the next VNc update.

For now, I just wanted to spark some discussion on the possibilities of uses for this feature; at the moment, it supports any MultiComponentList (all BaseMultis have this, via the Components property) - so you can not only display any multi, but you can also display custom house designs...
The final release will simply use a tile matrix, something like StaticTile[][][], to allow you to build any kind of tile-based structure in a gump, as if they were drawn in the client; I foresee it possibly being used for a new kind of Addon Designer. However, the code is quite simplistic at the moment and would require significant interface boilerplate code to function anything like a designer - for now, it just displays the tile matrix.

A code sample, for those who want to know;
C#:
	public class MultiDisplayGump : SuperGump
	{
		public static void Initialize()
		{
			CommandUtility.Register(
				"ViewMulti",
				AccessLevel.GameMaster,
				e => e.Mobile.BeginTarget<Server.Multis.HouseSign>(
					(m, s) =>
					{
						new MultiDisplayGump((PlayerMobile)m, s.House).Send();
					},
					null));
		}

		public BaseMulti Multi { get; set; }

		public MultiDisplayGump(PlayerMobile user, BaseMulti multi)
			: base(user)
		{
			Multi = multi;

			Modal = true;
		}

		protected override void Compile()
		{
			base.Compile();

			CanMove = true;
		}

		protected override void CompileLayout(SuperGumpLayout layout)
		{
			base.CompileLayout(layout);

			layout.Add("multi", () => AddMulti(0, 0, Multi, Multi.Hue));
		}

		public virtual void AddMulti(int x, int y, int multiID, int hue)
		{
			AddMulti(x, y, MultiExtUtility.GetComponents(multiID), hue);
		}

		public virtual void AddMulti(int x, int y, BaseMulti multi, int hue)
		{
			AddMulti(x, y, multi.Components, hue);
		}

		public virtual void AddMulti(int x, int y, MultiComponentList mcl, int hue)
		{
			x += 22 * mcl.Height;

			Point offset;
			int h;

			for (var xo = 0; xo < mcl.Width; xo++)
			{
				for (var yo = 0; yo < mcl.Height; yo++)
				{
					foreach (var t in mcl.Tiles[xo][yo].Where(t => t.ID > 1 && t.ID <= TileData.MaxItemValue)
													   .OrderBy(zt => zt.Z)
													   .ThenByDescending(zt => TileData.ItemTable[zt.ID].Flags.HasFlag(TileFlag.Surface))
													   .ThenByDescending(zt => TileData.ItemTable[zt.ID].Flags.HasFlag(TileFlag.Wall))
													   .ThenBy(zt => TileData.ItemTable[zt.ID].Flags.HasFlag(TileFlag.Roof))
													   .ThenBy(zt => TileData.ItemTable[zt.ID].CalcHeight))
					{
						offset = UltimaArtExt.GetImageOffset(t.ID);
						offset = new Point(((xo * 22) - (yo * 22)) + offset.X, (((yo * 22) + (xo * 22)) + offset.Y) - (t.Z * 4));

						h = t.Hue > 0 ? t.Hue : hue;

						if (h > 0)
						{
							AddItem(x + offset.X, y + offset.Y, t.ID, h);
						}
						else
						{
							AddItem(x + offset.X, y + offset.Y, t.ID);
						}
					}
				}
			}
		}
	}


So, what's on your mind now? :D

[EDIT]
Addition of the UltimaArtExt class code that I forgot to post originally with the above example - this code is required if you wish to implement this feature right now on your shard, but you must have Ultima.dll installed and the Ultima namespace available.
This code is responsible for the proper placement of tiles in gumps, it can also be used in various other places, for example, properly aligning items displayed as icons, without having to hard-code the image's offset.

C#:
#region Header
//   Voxpire    _,-'/-'/  ArtExt.cs
//   .      __,-; ,'( '/
//    \.    `-.__`-._`:_,-._       _ , . ``
//     `:-._,------' ` _,`--` -: `_ , ` ,' :
//        `---..__,,--'  (C) 2015  ` -'. -'
//        #  Vita-Nex [http://core.vita-nex.com]  #
//  {o)xxx|===============-   #   -===============|xxx(o}
//        #        The MIT License (MIT)          #
#endregion

#region References
using System.Drawing;

using Server.Items;

using Ultima;
#endregion

namespace Server
{
	public static class UltimaArtExt
	{
		public static Point GetImageOffset(int id)
		{
			var b = GetImageSize(id);

			int x = 0, y = 0;

			if (b.Width > 44)
			{
				x -= (b.Width - 44) / 2;
			}
			else if (b.Width < 44)
			{
				x += (44 - b.Width) / 2;
			}

			if (b.Height > 44)
			{
				y -= (b.Height - 44);
			}
			else if (b.Height < 44)
			{
				y += (44 - b.Height);
			}

			return new Point(x, y);
		}

		public static Size GetImageSize(int id)
		{
			var img = id >= 0 && id <= TileData.MaxItemValue ? Art.GetStatic(id) : null;

			if (img == null)
			{
				return new Size(44, 44);
			}

			return new Size(img.Width, img.Height);
		}

		public static Size GetImageSize(this Item item)
		{
			if (item == null || item is BaseMulti)
			{
				return new Size(44, 44);
			}

			return GetImageSize(item.ItemID);
		}

		public static Rectangle2D GetStaticBounds(int id)
		{
			var img = id >= 0 && id <= TileData.MaxItemValue ? Art.GetStatic(id) : null;

			if (img == null)
			{
				return new Rectangle2D(0, 0, 44, 44);
			}

			int xMin, xMax, yMin, yMax;
			Art.Measure(img, out xMin, out yMin, out xMax, out yMax);

			return new Rectangle2D(new Point2D(xMin, yMin), new Point2D(xMax, yMax));
		}

		public static Rectangle2D GetStaticBounds(this Item item)
		{
			if (item == null || item is BaseMulti)
			{
				return new Rectangle2D(0, 0, 44, 44);
			}

			return GetStaticBounds(item.ItemID);
		}
	}
}
 
Last edited:
Awesome! I'm not much of a scripter -but when I see this as being a gump to build on? Maybe being able to hue house panels or even a boat/as paint- or to have a custom house with an option for a player being able to create the house design. I could be way off with this, but you never know :)
 
Gumps do have their limits, but I haven't found a maximum for the amount of entries you can add to a gump, RunUO will disconnect clients due to "too much data pending" before the client even gets a chance to crash. The more common crashes caused by gumps are more due to the limits of each individual gump entry, for example, a background entry exceeding 800x600 can crash the client.

The OffsetSelectorGump in VNc generates about 20,000 buttons, which as a lot compared to this; the Minax Stronghold is the biggest Multi I could find and it worked like a charm.
The only time it crashed my client was when I didn't use a background with an appropriate size.

In the example, I used SuperGump's Modal property to ensure a large background to work with and made it movable to see how it renders off-screen.
Interestingly, I found a weird bug; if you send a gump with a 0,0 offset and it's children have a relative negative-x offset, there seems to be a threshold whereby the left edge of the gump will start appearing from the right side of the client window. (Could this, in fact, be used to design Right-To-Left gumps that are positioned on the far right side of the client?)

Are there any stress tests you'd like me to perform?

I could try to animate the flaming torches, lol.
 
Last edited:
Awesome! I'm not much of a scripter -but when I see this as being a gump to build on? Maybe being able to hue house panels or even a boat/as paint- or to have a custom house with an option for a player being able to create the house design. I could be way off with this, but you never know :)

Multi's hues are global, all the components in the Multi inherit that hue, so usually you can not change each individual multi tile; also, HouseFoundation packets don't support sending Hue information, sadly.

The first use case I will have for this will be house deeds, you won't have to double-click the deed to get a target-based preview, you could open a gump that displays a preview of the house instead, with a "place now" button, etc.
On my shard, it is possible to deed custom houses, the deeds retain the design and basically restore the design when the deed is placed; the downfall of this system is that when players purchase deeds for other player's custom house designs, they could just be buying a giant heap of shit, 4 floors worth of wall tiles, lol... can't have that, so I want people to be able to preview the house design that is saved within the deed :)

C#:
	public class ArchitectureDeed : HouseDeed
	{
		public static void Initialize()
		{
			CommandUtility.Register("GetHouseDeed", AccessLevel.GameMaster, e =>
			{
				var m = e.Mobile;

				if (m == null)
				{
					return;
				}

				m.SendMessage("Target a house sign to obtain a deed for a house...");

				m.Target = new ItemSelectTarget<HouseSign>((sm, s) =>
				{
					var h = s.House;

					if (h == null)
					{
						return;
					}

					var d = h.GetDeed();

					if (d != null)
					{
						d.GiveTo(sm);
					}
				}, sf => { });
			});
		}

		public MultiComponentList HouseComponents { get; private set; }
		public MultiTileEntry[] HouseFixtures { get; private set; }

		[CommandProperty(AccessLevel.GameMaster)]
		public int HouseLockdowns { get; set; }

		[CommandProperty(AccessLevel.GameMaster)]
		public int HouseSecures { get; set; }

		[CommandProperty(AccessLevel.GameMaster)]
		public int HousePrice { get; set; }

		[CommandProperty(AccessLevel.GameMaster)]
		public string HouseName { get; set; }

		[CommandProperty(AccessLevel.GameMaster)]
		public string HouseDesigner { get; set; }

		public override Rectangle2D[] Area
		{
			get { return new[] {new Rectangle2D(HouseComponents.Min, HouseComponents.Max)}; }
		}

		public ArchitectureDeed(HouseFoundation house)
			: this(
				house.Sign.Name, house.ItemID, Point3D.Zero, house.CurrentState.Components, house.CurrentState.Fixtures,
				house.MaxLockDowns, house.MaxSecures, house.Price)
		{
			var hpe = HousePlacementEntry.Find(house);

			if (hpe != null)
			{
				Offset = hpe.Offset;

				Name = hpe.Description.GetString();
			}
			else
			{
				Offset = house.Components.Center.ToPoint3D(house.Z).Clone3D(0, house.Components.Height / 2);
			}

			if (house.Owner != null)
			{
				HouseDesigner = house.Owner.RawName;
			}
		}

		public ArchitectureDeed(
			string name,
			int id,
			Point3D offset,
			MultiComponentList list,
			IEnumerable<MultiTileEntry> fixtures,
			int lockdowns,
			int secures,
			int price)
			: base(id, offset)
		{
			Name = "Architecture Deed";
			LootType = LootType.Blessed;

			HouseName = name;
			HouseComponents = new MultiComponentList(list);
			HouseFixtures = fixtures.ToArray();
			HouseLockdowns = lockdowns;
			HouseSecures = secures;
			HousePrice = price;
		}

		public ArchitectureDeed(Serial serial)
			: base(serial)
		{ }

		public override void OnAfterDuped(Item newItem)
		{
			base.OnAfterDuped(newItem);

			if (newItem is ArchitectureDeed)
			{
				var ad = (ArchitectureDeed)newItem;

				ad.HouseComponents = new MultiComponentList(HouseComponents);
				ad.HouseFixtures = HouseFixtures.ToArray();
			}
		}

		public override void GetProperties(ObjectPropertyList list)
		{
			base.GetProperties(list);

			var eopl = new ExtendedOPL(list);

			if (!String.IsNullOrWhiteSpace(HouseName))
			{
				eopl.Add("\"{0}\"".WrapUOHtmlColor(Color.Gold), HouseName);
			}

			if (!String.IsNullOrWhiteSpace(HouseDesigner))
			{
				eopl.Add("Designed By: {0}", HouseDesigner);
			}

			eopl.Add("Worth: {0:#,0} Gold", FormatProperty(HousePrice));
			eopl.Add("Lockdowns: {0:#,0}", FormatProperty(HouseLockdowns));
			eopl.Add("Secures: {0:#,0}", FormatProperty(HouseSecures));

			eopl.Apply();
		}

		public override BaseHouse GetHouse(Mobile owner)
		{
			var house = new HouseFoundation(owner, MultiID, HouseLockdowns, HouseSecures)
			{
				Price = HousePrice,
				Sign =
				{
					Name = HouseName
				}
			};

			var state = new DesignState(house, HouseComponents);

			foreach (var t in HouseFixtures)
			{
				state.Components.Add(t.m_ItemID, t.m_OffsetX, t.m_OffsetY, t.m_OffsetZ);
			}

			state.MeltFixtures();

			house.ClearFixtures(owner);
			house.AddFixtures(owner, state.Fixtures);

			state.FreezeFixtures();

			house.CurrentState = state;
			house.DesignState = new DesignState(state);
			house.BackupState = new DesignState(state);

			house.CheckSignpost();

			return house;
		}

		public override void Serialize(GenericWriter writer)
		{
			base.Serialize(writer);

			var version = writer.SetVersion(1);

			switch (version)
			{
				case 1:
				{
					writer.WriteArray(HouseFixtures, (w, f) =>
					{
						w.Write(f.m_ItemID);
						w.Write(f.m_OffsetX);
						w.Write(f.m_OffsetY);
						w.Write(f.m_OffsetZ);
						w.Write(f.m_Flags);
					});
				}
					goto case 0;
				case 0:
				{
					HouseComponents.Serialize(writer);

					writer.Write(HouseName);
					writer.Write(HouseDesigner);
					writer.Write(HouseLockdowns);
					writer.Write(HouseSecures);
					writer.Write(HousePrice);
				}
					break;
			}
		}

		public override void Deserialize(GenericReader reader)
		{
			base.Deserialize(reader);

			var version = reader.GetVersion();

			switch (version)
			{
				case 1:
				{
					HouseFixtures =
						reader.ReadArray(r => new MultiTileEntry(r.ReadUShort(), r.ReadShort(), r.ReadShort(), r.ReadShort(), r.ReadInt()));
				}
					goto case 0;
				case 0:
				{
					HouseComponents = new MultiComponentList(reader);

					HouseName = reader.ReadString();
					HouseDesigner = reader.ReadString();
					HouseLockdowns = reader.ReadInt();
					HouseSecures = reader.ReadInt();
					HousePrice = reader.ReadInt();
				}
					break;
			}

			if (HouseFixtures == null)
			{
				HouseFixtures = new MultiTileEntry[0];
			}
		}
	}
 
Last edited:
Agree, great idea with being able to preview the house design that is saved within the deed - I'm sure there will be idea's which will come forth with this idea of the Multis!! :)
 
@Voxpire
Great stuff here dude! Just wanted to add my two cents in regards to multi hues...I investigated this issue a few years ago. We know that Hue exists for items and individual walls, doors, etc added in-game can be hued. Also entire multis can be hued (most look absolutely horrendous unless you are an expert at creating custom hues). The Hue Flag is visible in Fiddler too, so it's there, it just needs to be added in the right place. I don't think anyone has tried tackling this yet and it's a tad out of my scope, but if you start in the Server folder, I believe adding Hue to the MultiData.cs file can be achieved by going coding it for the MultiComponent itself.
 
@Voxpire

how did you get that tree to show up in a paperdoll?
might be able to get those new backpacks to show up too
The tree is not in the paperdoll, its part of the multi that happened to be shown over the paperdoll
TO display the new backpacks you just need to change the itemid/gumpid
 
i've been able to get the container gump to show just fine, but there's no Item associated with the backpack gump art

from what i've understood, short of changing the files, there's no way to change how an item appears on the paperdoll without changing it's itemid
 
@Voxpire
Great stuff here dude! Just wanted to add my two cents in regards to multi hues...I investigated this issue a few years ago. We know that Hue exists for items and individual walls, doors, etc added in-game can be hued. Also entire multis can be hued (most look absolutely horrendous unless you are an expert at creating custom hues). The Hue Flag is visible in Fiddler too, so it's there, it just needs to be added in the right place. I don't think anyone has tried tackling this yet and it's a tad out of my scope, but if you start in the Server folder, I believe adding Hue to the MultiData.cs file can be achieved by going coding it for the MultiComponent itself.

MultiComponentList stores a tile matrix in the form of StaticTile[][][] - StaticTile has a Hue property, so each tile in an MCL can have a hue defined - and they would work fine in the gumps, but when it comes to HouseFoundations, the DesignState Packets literally do not support hues, the client does not read any of the packet buffer as being a usable hue, IE; it's not in the structure of the packet.
All those packets care about is tile ID and relative X,Y,Z position.
If you like, you can insert a pre-hued item in to a house design using [DesignInsert, but the inserted item will not retain its hue.
Things could have changed with recent clients though, I'm willing to see if it is now possible. :)
 
MultiComponentList stores a tile matrix in the form of StaticTile[][][] - StaticTile has a Hue property, so each tile in an MCL can have a hue defined - and they would work fine in the gumps, but when it comes to HouseFoundations, the DesignState Packets literally do not support hues, the client does not read any of the packet buffer as being a usable hue, IE; it's not in the structure of the packet.
All those packets care about is tile ID and relative X,Y,Z position.
If you like, you can insert a pre-hued item in to a house design using [DesignInsert, but the inserted item will not retain its hue.
Things could have changed with recent clients though, I'm willing to see if it is now possible. :)

I was thinking of tackling it a different way...after the house is built. Everything becomes a static (we're talking about custom houses now), but statics don't have a hue field. That's more along the lines of what I was talking about for adding a hue: carrying the hue over to a static.

What would be nice, along the lines of what you were originally talking about is:

Currently, when designing new construction, not using the house customization tool, I have to switch between Fiddler and Pandora (I prefer Pandora's hue picker over Fiddler's). If there were a gump that would still give you an item display (complete with itemID and thumbnail) and also offer a preview with a hue, that would be more than helpful! I'm not sure how much the client would appreciate being slammed with item displays, so there would probably have to be some paging applied with item thumbnails loaded only as the page was loaded (that would also mean probably adding some kind of shortcuts to hop around the entire item list faster).

I just thought of something else too....can anyone confirm or deny if my memory serves or not....on OSI, were "we" able to see item hue in the crafting window? I could have sworn that when you went to craft anything, if you used a hued wood, ingot, stone, etc, the preview would inherit the hue so you could see it prior to actually crafting.

On a side note to the above: I always thought it would be nice if the hue of a crafted item was also based on the crafter's skill. For example, say you wanted a royal blue helm. So you'd use Valorite to begin with, but the actual crafted hue would be a "lesser" shade of blue, unless you were say Legendary.
 
I just thought of something else too....can anyone confirm or deny if my memory serves or not....on OSI, were "we" able to see item hue in the crafting window? I could have sworn that when you went to craft anything, if you used a hued wood, ingot, stone, etc, the preview would inherit the hue so you could see it prior to actually crafting.

On a side note to the above: I always thought it would be nice if the hue of a crafted item was also based on the crafter's skill. For example, say you wanted a royal blue helm. So you'd use Valorite to begin with, but the actual crafted hue would be a "lesser" shade of blue, unless you were say Legendary.

I don't recall the item in the window showing the hue.

I do remember that in the second age, maybe later and maybe still that crafted items had a few varying shades for each resource.
 
I was thinking of tackling it a different way...after the house is built. Everything becomes a static (we're talking about custom houses now), but statics don't have a hue field. That's more along the lines of what I was talking about for adding a hue: carrying the hue over to a static.

When you target a Land or Static map tile, like via [props, the server translates the tile into a LandTarget or StaticTarget object, which is what is displayed in the PropertiesGump.
This happens for all Targets, fortunately, LandTarget and StaticTarget implement IPoint3D, which is why Target.OnTarget often casts object to IPoint3D at first.
Targeting any tile in a Multi does the same thing.
The StaticTarget object does not contain a Hue property, but one could be added based on the fact that StaticTarget wraps StaticTile and StaticTile does have a Hue property;
C#:
	public class StaticTarget : IPoint3D
	{
		private Point3D m_Location;
		private readonly int m_ItemID;
		private readonly int m_Hue;

		public StaticTarget(Point3D location, int itemID, int hue)
		{
			m_Location = location;
			m_ItemID = itemID & TileData.MaxItemValue;
			m_Hue = hue;
			m_Location.Z += TileData.ItemTable[m_ItemID].CalcHeight;
		}

		[CommandProperty(AccessLevel.Counselor)]
		public Point3D Location { get { return m_Location; } }

		[CommandProperty(AccessLevel.Counselor)]
		public string Name { get { return TileData.ItemTable[m_ItemID].Name; } }

		[CommandProperty(AccessLevel.Counselor)]
		public TileFlag Flags { get { return TileData.ItemTable[m_ItemID].Flags; } }

		[CommandProperty(AccessLevel.Counselor)]
		public int X { get { return m_Location.X; } }

		[CommandProperty(AccessLevel.Counselor)]
		public int Y { get { return m_Location.Y; } }

		[CommandProperty(AccessLevel.Counselor)]
		public int Z { get { return m_Location.Z; } }

		[CommandProperty(AccessLevel.Counselor)]
		public int ItemID { get { return m_ItemID; } }

		[CommandProperty(AccessLevel.Counselor)]
		public int Hue { get { return m_Hue; } }
	}

In PacketHandlers.TargetResponse method;
C#:
									StaticTile[] tiles = map.Tiles.GetStaticTiles(x, y, !t.DisallowMultis);
									int hue = 0;
									bool valid = false;

									if (state.HighSeas)
									{
										ItemData id = TileData.ItemTable[graphic & TileData.MaxItemValue];
										if (id.Surface)
										{
											z -= id.Height;
										}
									}

									for (int i = 0; !valid && i < tiles.Length; ++i)
									{
										if (tiles[i].Z == z && tiles[i].ID == graphic)
										{
											hue = tiles[i].Hue;
											valid = true;
										}
									}

									if (!valid)
									{
										t.Cancel(from, TargetCancelType.Canceled);
										return;
									}
									else
									{
										toTarget = new StaticTarget(new Point3D(x, y, z), graphic, hue);
									}

I'm still not sure if there is a point to this edit though, the client is limited, does the world actually have baked-in, hued static tiles?

What would be nice, along the lines of what you were originally talking about is:

Currently, when designing new construction, not using the house customization tool, I have to switch between Fiddler and Pandora (I prefer Pandora's hue picker over Fiddler's). If there were a gump that would still give you an item display (complete with itemID and thumbnail) and also offer a preview with a hue, that would be more than helpful! I'm not sure how much the client would appreciate being slammed with item displays, so there would probably have to be some paging applied with item thumbnails loaded only as the page was loaded (that would also mean probably adding some kind of shortcuts to hop around the entire item list faster).

In-game UOFiddler? Totally possible, even without the features of OP. Everything in OP works without Ultima.dll, but you have to sacrifice proper y-axis placement of art that is larger than 44x44 pixels (because gumps draw items and images from the top-left, and the client requires their y-axis to be decreased based on how much taller than 44 pixels the image is)
 
Edited OP; Added the UltimaArtExt class that I forgot to include with the OP, it is essential for the example to function correctly.
 
Back