diff --git a/Projects/Server/Gumps/Grid/Grid.cs b/Projects/Server/Gumps/Grid/Grid.cs
new file mode 100644
index 000000000..2cdb8b075
--- /dev/null
+++ b/Projects/Server/Gumps/Grid/Grid.cs
@@ -0,0 +1,77 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2022 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: Grid.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+using System.Collections.Generic;
+
+namespace Server.Gumps;
+
+public enum GridHues
+{
+ White = 2049, // Change to 0x480 if you have old hues.mul
+ Black = 0,
+}
+
+public static class GridColors
+{
+ public const string Gold = "#ffc959";
+ public const string White = "#ffffff";
+ public const string Blue = "#1D63DC";
+ public const string LightBlue = "#7799EE";
+ public const string Green = "#80E732";
+ public const string Orange = "#F28B00";
+ public const string Purple = "#F3ACF6";
+ public const string Red = "#B52B2D";
+ public const string LightGreen = "#E6FFC0";
+ public const string Yellow = "#FFFFBB";
+}
+
+public class ListItem
+{
+ public int X { get; set; }
+ public int Y { get; set; }
+ public int Index { get; set; }
+ public Col[] Cols { get; set; }
+}
+
+public class Col
+{
+ public int X { get; set; }
+ public int Width { get; set; }
+ public int HCenter => X + Width / 2;
+ public int HEnd => X + Width;
+}
+
+public class Row
+{
+ public int Y { get; set; }
+ public int Height { get; set; }
+ public int VCenter => Y + Height / 2;
+ public int VEnd => Y + Height;
+}
+
+public class Grid
+{
+ public string Name { get; set; }
+ public int Height { get; set; }
+ public int Width { get; set; }
+ public List
Columns { get; set; } = new();
+ public List Rows { get; set; } = new();
+}
+
+public class Swap
+{
+ public int Index { get; set; }
+ public int Size { get; set; }
+}
diff --git a/Projects/Server/Gumps/Grid/GumpGrid.cs b/Projects/Server/Gumps/Grid/GumpGrid.cs
new file mode 100644
index 000000000..1c5d87118
--- /dev/null
+++ b/Projects/Server/Gumps/Grid/GumpGrid.cs
@@ -0,0 +1,525 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2022 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: GumpGrid.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using Server.Collections;
+
+namespace Server.Gumps;
+
+public class GumpGrid : Gump
+{
+ private Dictionary _grids = new();
+
+ public GumpGrid(int x, int y) : base(x, y)
+ {
+ }
+
+ private void AddColumn(string name, int x, int width)
+ {
+ _grids[name].Columns.Add(new Col { X = x, Width = width, });
+ }
+
+ private void AddRow(string name, int y, int height)
+ {
+ _grids[name].Rows.Add(new Row { Y = y, Height = height });
+ }
+
+ private void InitColumn(string name, int column, int w, int x)
+ {
+ var width = w / column;
+ for (int i = 0; i < column; i++)
+ {
+ AddColumn(name, i * width + x, width);
+ }
+ }
+
+ private void InitRow(string name, int row, int h, int y)
+ {
+ var height = h / row;
+ for (int i = 0; i < row; i++)
+ {
+ AddRow(name, i * height + y, height);
+ }
+ }
+
+ private static Swap Exist(ref PooledRefList list, int index)
+ {
+ for (int i = 0; i < list.Count; i++)
+ {
+ var item = list[i];
+ if (item.Index == index)
+ {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ private static int CalculateCustom(int column, int len, string[] sizes, ref PooledRefList list)
+ {
+ var cropSize = 0;
+
+ for (int i = 0; i < column; i++)
+ {
+ if (sizes[i] == "*")
+ {
+ continue;
+ }
+
+ //percent
+ var percent = sizes[i].Replace("*", "");
+ var size = sizes[i] != percent ? len / 100 * int.Parse(percent) : int.Parse(percent);
+
+ list.Add(new Swap { Index = i, Size = size });
+ cropSize += size;
+ }
+
+ return cropSize;
+ }
+ private void InitCustomSizeRow(string name, int height, int row, string[] sizes, int y)
+ {
+ var list = PooledRefList.Create();
+ var cropHeight = CalculateCustom(row, height, sizes, ref list);
+ var Height = height - cropHeight;
+ var factor = row - list.Count;
+ for (int i = 0; i < row; i++)
+ {
+ var swap = Exist(ref list, i);
+ if (swap != null)
+ {
+ var size = swap.Size;
+ if (i == 0)
+ {
+ AddRow(name, y, size);
+ }
+ else
+ {
+ var col = _grids[name].Rows[i - 1];
+ AddRow(name, col.Height + col.Y + y, size);
+ }
+ }
+ else
+ {
+ var size = Height / factor;
+ if (i == 0)
+ {
+ AddRow(name, y, size);
+ }
+ else
+ {
+ var col = _grids[name].Rows[i - 1];
+ AddRow(name, col.Height + col.Y + y, size);
+ }
+ }
+ }
+
+ list.Dispose();
+ }
+
+ private void InitCustomSizeColumn(string name, int width, int column, string[] sizes, int x)
+ {
+ var list = PooledRefList.Create();
+ var cropWidth = CalculateCustom(column, width, sizes, ref list);
+ var Width = width - cropWidth;
+ var factor = column - list.Count;
+
+ for (int i = 0; i < column; i++)
+ {
+ var swap = Exist(ref list, i);
+ if (swap != null)
+ {
+ var size = swap.Size;
+ if (i == 0)
+ {
+ AddColumn(name, x, size);
+ }
+ else
+ {
+ var col = _grids[name].Columns[i - 1];
+ AddColumn(name, col.Width + col.X + x, size);
+ }
+ }
+ else
+ {
+ var size = Width / factor;
+ if (i == 0)
+ {
+ AddColumn(name, x, size);
+ }
+ else
+ {
+ var col = _grids[name].Columns[i - 1];
+ AddColumn(name, col.Width + col.X + x, size);
+ }
+ }
+ }
+ }
+
+ public Grid SubGrid(
+ string name,
+ string destGridName,
+ int columnStart,
+ int rowStart,
+ int createColumnsCount,
+ int createRowsCount,
+ int columnSpan = 0,
+ int rowSpan = 0,
+ string createColumnSize = "",
+ string createRowSize = "",
+ int marginX = 0,
+ int marginY = 0
+ )
+ {
+ if (CalculateCord(
+ destGridName,
+ columnStart,
+ rowStart,
+ columnSpan,
+ rowSpan,
+ out var x,
+ out var y,
+ out var width,
+ out var height
+ ))
+ {
+ Grid(
+ name,
+ width,
+ height,
+ createColumnsCount,
+ createRowsCount,
+ createColumnSize,
+ createRowSize,
+ x + marginX,
+ y + marginY
+ );
+ }
+
+ return CreateGrid(name, 0, 0);
+ }
+
+ public Grid CreateGrid(string name, int width, int height)
+ {
+ if (!_grids.TryGetValue(name, out var grid))
+ {
+ grid = new Grid { Name = name, Width = width, Height = height };
+ _grids.Add(name, grid);
+ }
+
+ return grid;
+ }
+
+ public Grid Grid(
+ string name,
+ int width,
+ int height,
+ int columns,
+ int rows,
+ string columnSize = "",
+ string rowSize = "",
+ int x = 0,
+ int y = 0
+ )
+ {
+ if (columns < 1)
+ {
+ throw new Exception(nameof(columns));
+ }
+
+ if (rows < 1)
+ {
+ throw new Exception(nameof(columns));
+ }
+
+ var Grid = CreateGrid(name, width, height);
+
+ if (columnSize.Length > 0)
+ {
+ var buffer = columnSize.Split(' ');
+ if (buffer.Length == columns)
+ {
+ InitCustomSizeColumn(name, width, columns, buffer, x);
+ }
+ else
+ {
+ throw new Exception(nameof(columnSize));
+ }
+ }
+ else
+ {
+ InitColumn(name, columns, width, x);
+ }
+
+ if (rowSize.Length > 0)
+ {
+ var buffer = rowSize.Split(' ');
+ if (buffer.Length == rows)
+ {
+ InitCustomSizeRow(name, height, rows, buffer, y);
+ }
+ else
+ {
+ throw new Exception(nameof(rowSize));
+ }
+ }
+ else
+ {
+ InitRow(name, rows, height, y);
+ }
+
+ return Grid;
+ }
+
+ public bool CalculateCord(
+ string name,
+ int column,
+ int row,
+ int columnSpan,
+ int rowSpan,
+ out int x,
+ out int y,
+ out int width,
+ out int height
+ )
+ {
+ x = 0; y = 0; width = 0; height = 0;
+ if (_grids.TryGetValue(name, out var grid))
+ {
+ var count = columnSpan == 0 ? column + 1 : row + columnSpan;
+ for (int i = column; i < count; i++)
+ {
+ if (i >= grid.Columns.Count)
+ {
+ break;
+ }
+
+ var dcol = grid.Columns[i];
+ if (i == column)
+ {
+ x = dcol.X;
+ }
+
+ width += dcol.Width;
+ }
+
+ count = rowSpan == 0 ? row + 1 : row + rowSpan;
+ for (int i = row; i < count; i++)
+ {
+ if (i >= grid.Rows.Count)
+ {
+ break;
+ }
+
+ var drow = grid.Rows[i];
+ if (i == row)
+ {
+ y = drow.Y;
+ }
+
+ height += drow.Height;
+ }
+
+ if (width == 0)
+ {
+ width = grid.Width;
+ }
+
+ if (height == 0)
+ {
+ height = grid.Height;
+ }
+
+ return true;
+
+ }
+
+ return false;
+ }
+
+ public class ListView
+ {
+ public int LineCount { get; }
+ public int ItemsCount { get; }
+ public ListItem[] Items { get; }
+ public ListItem Header { get; }
+ public int Page { get; }
+ public int TotalPages { get; }
+ public int ColHeight { get; }
+ public bool CanNext { get; }
+ public bool CanBack { get; }
+ public int ColsCount { get; }
+
+ public ListView(
+ int itemsCount,
+ int columns,
+ int heightPerItem,
+ int page,
+ int x,
+ int y,
+ int height,
+ int pageItemCount,
+ int[] ColWidth,
+ int marginX = 0,
+ int marginY = 0,
+ int headerHeight = 0
+ )
+ {
+ ItemsCount = itemsCount;
+ var itemsPerPageCount = pageItemCount == 0 ? (height - marginY) / heightPerItem : pageItemCount;
+ LineCount = itemsPerPageCount;
+
+ if (itemsCount > 0)
+ {
+ TotalPages = itemsCount / itemsPerPageCount;
+ if (itemsCount % itemsPerPageCount == 0)
+ {
+ TotalPages--;
+ }
+ }
+
+ Page = page > TotalPages ? TotalPages : page;
+ ColsCount = columns;
+ ColHeight = heightPerItem;
+
+ var index = Page * itemsPerPageCount > 0 ? Page * itemsPerPageCount : 0;
+ var nowPageItems = index + itemsPerPageCount;
+
+ if (index > 0)
+ {
+ CanBack = true;
+ }
+
+ if (nowPageItems < itemsCount)
+ {
+ CanNext = true;
+ }
+
+ if (nowPageItems > itemsCount)
+ {
+ itemsPerPageCount = Page > 0 ? itemsCount - Page * itemsPerPageCount : itemsCount;
+ }
+
+ //header
+ Header = new ListItem
+ {
+ X = x,
+ Y = y + marginY,
+ Index = -1,
+ Cols = new Col[columns],
+ };
+
+ var length = x + marginX;
+ for (int col = 0; col < columns; col++)
+ {
+ Header.Cols[col] = new Col();
+ Header.Cols[col].Width = ColWidth[col];
+ Header.Cols[col].X = length;
+ length += ColWidth[col];
+ }
+
+ Items = new ListItem[itemsPerPageCount];
+
+ for (int i = 0; i < itemsPerPageCount; i++)
+ {
+ Items[i] = new ListItem
+ {
+ X = x,
+ Y = y + i * heightPerItem + marginY + headerHeight,
+ Index = index + i,
+ Cols = Header.Cols
+ };
+ }
+ }
+ }
+
+ public ListView AddListView(
+ string name,
+ int column,
+ int row,
+ int itemsCount,
+ int page,
+ int heightPerItem,
+ int createColumn,
+ int columnSpan = 0,
+ int rowSpan = 0,
+ int width = 0,
+ int height = 0,
+ int pageItemCount = 0,
+ int marginX = 0,
+ int marginY = 0,
+ int headerHeight = 0,
+ string colSize = ""
+ )
+ {
+ if (CalculateCord(name, column, row, columnSpan, rowSpan, out var x, out var y, out var w, out var h))
+ {
+ w = width > 0 ? width : w;
+ h = height > 0 ? height : h;
+
+ int[] colWidth = new int[createColumn];
+
+ if (colSize == string.Empty)
+ {
+ for (int i = 0; i < createColumn; i++)
+ {
+ colWidth[i] = w / createColumn;
+ }
+ }
+ else
+ {
+ var buffer = colSize.Split(' ');
+
+ if (buffer.Length > 0)
+ {
+ const string listName = "listView";
+ _grids.Add(listName, new Grid());
+
+ if (createColumn < buffer.Length)
+ {
+ Array.Resize(ref buffer, createColumn);
+ }
+
+ InitCustomSizeColumn(listName, w, createColumn, buffer, x);
+ var cols = _grids[listName].Columns;
+ for (var i = 0; i < createColumn; i++)
+ {
+ colWidth[i] = cols[i].Width;
+ }
+
+ _grids.Remove(listName);
+ }
+ }
+
+ return new ListView(
+ itemsCount,
+ createColumn,
+ heightPerItem,
+ page,
+ x,
+ y,
+ h,
+ pageItemCount,
+ colWidth,
+ marginX,
+ marginY,
+ headerHeight
+ );
+ }
+
+ return null;
+ }
+}
diff --git a/Projects/Server/Gumps/Gump.cs b/Projects/Server/Gumps/Gump.cs
index f3ccb13a4..163faada3 100644
--- a/Projects/Server/Gumps/Gump.cs
+++ b/Projects/Server/Gumps/Gump.cs
@@ -2,10 +2,11 @@ using System;
using System.Collections.Generic;
using Server.Network;
using Server.Text;
+using Server.Utilities;
namespace Server.Gumps;
-public class Gump
+public partial class Gump
{
private static Serial _nextSerial = (Serial)1;
@@ -165,6 +166,11 @@ public class Gump
Add(new GumpLabel(x, y, hue, text));
}
+ public void AddLabelHtml(int x, int y, int width, int height, string text, string hue, int size = 4, bool center = true)
+ {
+ AddHtml(x, y, width, height, center ? text.Center(hue, size) : text.Color(hue, size));
+ }
+
public void AddLabelCropped(int x, int y, int width, int height, int hue, string text)
{
Add(new GumpLabelCropped(x, y, width, height, hue, text));
diff --git a/Projects/Server/Utilities/Html.cs b/Projects/Server/Utilities/Html.cs
new file mode 100644
index 000000000..8de7a6de9
--- /dev/null
+++ b/Projects/Server/Utilities/Html.cs
@@ -0,0 +1,48 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2022 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: Html.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+using System.Runtime.CompilerServices;
+
+namespace Server.Utilities;
+
+public static class Html
+{
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string Color(this string text, int color) => $"{text}";
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string Color(this string text, int color, int size) => $"{text}";
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string Color(this string text, string color) => $"{text}";
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string Color(this string text, string color, int size) => $"{text}";
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string Center(this string text) => $"{text}";
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string Center(this string text, int color) => $"{text}";
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string Center(this string text, int color, int size) => $"{text}";
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string Center(this string text, string color) => $"{text}";
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string Center(this string text, string color, int size) => $"{text}";
+}
diff --git a/Projects/UOContent/Engines/Spawners/BaseSpawner.cs b/Projects/UOContent/Engines/Spawners/BaseSpawner.cs
index 130e21c24..e4faa6332 100644
--- a/Projects/UOContent/Engines/Spawners/BaseSpawner.cs
+++ b/Projects/UOContent/Engines/Spawners/BaseSpawner.cs
@@ -250,6 +250,11 @@ public abstract class BaseSpawner : Item, ISpawner
Region ISpawner.Region => Region.Find(Location, Map);
+ public void UpdateEntries(List entries)
+ {
+ Entries = entries;
+ }
+
public void Remove(ISpawnable spawn)
{
Defrag();
diff --git a/Projects/UOContent/Engines/Spawners/SpawnerControllerGump.cs b/Projects/UOContent/Engines/Spawners/SpawnerControllerGump.cs
new file mode 100644
index 000000000..48c113c50
--- /dev/null
+++ b/Projects/UOContent/Engines/Spawners/SpawnerControllerGump.cs
@@ -0,0 +1,580 @@
+using System;
+using Server.Collections;
+using Server.Network;
+using Server.Engines.Spawners;
+
+namespace Server.Gumps;
+
+public enum SpawnSearchType
+{
+ Creature = 0,
+ Coords,
+ Props,
+ Name
+}
+public class SpawnSearch
+{
+ public SpawnSearchType Type { get; set; } = SpawnSearchType.Creature;
+ public string SearchPattern { get; set; } = "";
+}
+
+public class SpawnerControllerGump : GumpGrid
+{
+ private BaseSpawner _copy;
+ private int _page;
+ private const int EntryCount = 5;
+ private Mobile _mobile;
+ private SpawnSearch _search;
+ private int _lineCount;
+ private BaseSpawner[] _spawners = Array.Empty();
+ private Grid _main;
+ private const int TypeCount = 15;
+
+ public static int GetButtonID(int type, int index) => 1 + type + index * TypeCount;
+
+ public static void Initialize()
+ {
+ CommandSystem.Register("Spawn", AccessLevel.GameMaster, OpenSpawnGump_OnCommand);
+ }
+
+ public static void OpenSpawnGump_OnCommand(CommandEventArgs e)
+ {
+ e.Mobile.SendGump(new SpawnerControllerGump(e.Mobile));
+ }
+
+ public void DrawBorder(ListView list)
+ {
+ AddBackground(0, 0, _main.Width, _main.Height, 1755);
+
+ var row1Y = _main.Rows[1].Y;
+ AddImageTiled(0, row1Y, _main.Width, 3, 1756);
+ AddAlphaRegion(0, row1Y, _main.Width, 3);
+
+ AddImageTiled(0, row1Y + 21, _main.Width, 3, 1756);
+ AddAlphaRegion(0, row1Y + 21, _main.Width, 3);
+
+ for (int i = 0; i < list.ColsCount - 1; i++)
+ {
+ var x = list.Header.Cols[i].HEnd - 8;
+ var y = list.Header.Y + 10;
+ var height = 20;
+ AddImageTiled(x, y, 3, height, 1758);
+ AddAlphaRegion(x, y, 3, height);
+ }
+
+ AddImageTiled(0, _main.Rows[2].Y - 11, _main.Width, 6, 1756);
+ AddImageTiled(0, _main.Rows[2].Y + 55, _main.Width, 6, 1756);
+ }
+
+ private static string ExtractCoords(Point3D cord) => $"X {cord.X} Y {cord.Y}";
+
+ public void DrawSearch()
+ {
+ const int deltaX = 140;
+ var search = SubGrid("search", _main.Name, 0, 0, 2, 1);
+
+ var col0X = search.Columns[0].X;
+ var col1X = search.Columns[1].X;
+ var col1HCenter = search.Columns[1].HCenter;
+ var row0Y = search.Rows[0].Y;
+ var row0VCenter = search.Rows[0].VCenter;
+
+ AddLabelHtml(col0X, row0Y + 8, col1X, 30, "Search type", GridColors.Gold);
+ AddLabelHtml(col1X, row0Y + 8, col1X, 30, "Search Match", GridColors.Gold);
+
+ AddImageTiled(0, row0Y + 30, _main.Width, 3, 1756);
+ AddImageTiled(col1X, row0Y, 3, search.Height, 1758);
+
+ var creature = _search.Type == SpawnSearchType.Creature ? 9021 : 9020;
+ var cords = _search.Type == SpawnSearchType.Coords ? 9021 : 9020;
+ var props = _search.Type == SpawnSearchType.Props ? 9021 : 9020;
+ var args = _search.Type == SpawnSearchType.Name ? 9021 : 9020;
+
+ AddButton(col0X + 10, row0Y + 45, creature, creature, GetButtonID(1, 0));
+ AddLabelHtml(col0X + 15, row0Y + 48, 100, 30, "Creature", GridColors.White);
+
+
+ AddButton(col0X + 10 + deltaX - 40, row0Y + 45, cords, cords, GetButtonID(1, 1));
+ AddLabelHtml(col0X + 40 + deltaX - 40, row0Y + 48, 150, 30, "Range", GridColors.White, 4, false);
+
+ AddButton(col0X + 10 + deltaX * 2 - 100, row0Y + 45, props, props, GetButtonID(1, 2));
+ AddLabelHtml(col0X + 40 + deltaX * 2 - 100, row0Y + 48, 150, 30, "Creature Props", GridColors.White, 4, false);
+
+ AddButton(col0X + 10 + deltaX * 3 - 100, row0Y + 45, args, args, GetButtonID(1, 3));
+ AddLabelHtml(col0X + 40 + deltaX * 3 - 100, row0Y + 48, 150, 30, "Spawner Name", GridColors.White, 4, false);
+
+ AddImageTiled(col1HCenter - 100, row0VCenter + 16, 200, 1, 9357);
+ AddTextEntry(col1HCenter - 100, row0VCenter, 200, 20, (int)GridHues.White, 0xFFFF, _search.SearchPattern, GetButtonID(1, 4));
+ AddButton(col1HCenter + 120, row0VCenter, 4023, 4024, GetButtonID(1, 4));
+
+ var type = (int)_search.Type;
+ if (type == 0)
+ {
+ AddLabelHtml(col1X, row0VCenter + 20, search.Columns[1].Width, 30, "Search by creature name", GridColors.White);
+ }
+ else if (type == 1)
+ {
+ AddLabelHtml(col1X, row0VCenter + 20, search.Columns[1].Width, 30, "Search by range (number)", GridColors.White);
+ }
+ else if (type == 2)
+ {
+ AddLabelHtml(col1X, row0VCenter + 20, search.Columns[1].Width, 30, "Search by entry property field", GridColors.White);
+ }
+ else if (type == 3)
+ {
+ AddLabelHtml(col1X, row0VCenter + 20, search.Columns[1].Width, 30, "Search by spawner name", GridColors.White);
+ }
+ }
+
+ private void SearchSpawner()
+ {
+ if (_search.SearchPattern.Length < 1)
+ {
+ return;
+ }
+
+ using var queue = PooledRefQueue.Create();
+
+ if (!(_search.Type == SpawnSearchType.Coords && int.TryParse(_search.SearchPattern, out var range)))
+ {
+ range = -1;
+ }
+
+ foreach (var item in World.Items.Values)
+ {
+ if (item is not BaseSpawner spawner)
+ {
+ continue;
+ }
+
+ bool enqueue = _search.Type switch
+ {
+ SpawnSearchType.Creature => SearchSpawnerCreatures(spawner, _search.SearchPattern),
+ SpawnSearchType.Coords => _mobile.InRange(spawner.Location, range),
+ SpawnSearchType.Props => SearchSpawnerProperties(spawner, _search.SearchPattern),
+ SpawnSearchType.Name => spawner.Name?.InsensitiveContains(_search.SearchPattern) == true,
+ _ => false
+ };
+
+ if (enqueue)
+ {
+ queue.Enqueue(spawner);
+ }
+ }
+
+ _spawners = queue.ToArray();
+ }
+
+ private static bool SearchSpawnerCreatures(BaseSpawner spawner, string searchPattern)
+ {
+ foreach (var entry in spawner.Entries)
+ {
+ if (entry.SpawnedName?.InsensitiveContains(searchPattern) == true)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool SearchSpawnerProperties(BaseSpawner spawner, string searchPattern)
+ {
+ foreach (var entry in spawner.Entries)
+ {
+ if (entry.Properties?.InsensitiveContains(searchPattern) == true)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void DrawFooter(ListView list)
+ {
+ if (list.CanNext)
+ {
+ AddButton(_main.Columns[0].HCenter + 60, _main.Rows[2].VCenter + 16, 4005, 4006, GetButtonID(10, 0));
+ }
+
+ if (list.CanBack)
+ {
+ AddButton(_main.Columns[0].HCenter + -60, _main.Rows[2].VCenter + 16, 4014, 4015, GetButtonID(11, 0));
+ }
+
+ var col0X = _main.Columns[0].X;
+ var col0Width = _main.Columns[0].Width;
+ var row2Y = _main.Rows[2].Y;
+
+ //past to all spawners
+ AddButton(col0X + 15, row2Y, 4029, 4031, GetButtonID(1, 5));
+ AddLabelHtml(col0X + 55, row2Y + 4, col0Width, 20, $"Paste props for {_spawners.Length} spawners", GridColors.Orange, 4, false);
+
+ //paste target
+ AddButton(col0X + 15, row2Y + 28, 4029, 4031, GetButtonID(1, 6));
+ AddLabelHtml(col0X + 55, row2Y + 32, col0Width, 20, $"Paste entry for {_spawners.Length} spawners", GridColors.Orange, 4, false);
+
+ //paste ground
+ AddButton(col0X + 15 + 260, row2Y, 4029, 4031, GetButtonID(1, 7));
+ AddLabelHtml(col0X + 55 + 260, row2Y + 4, col0Width, 20, "Paste copy to the ground", GridColors.Yellow, 4, false);
+
+ AddLabelHtml(col0X + 16, _main.Rows[2].VCenter + 17, col0Width, 20, $"Pages {list.Page + 1}/{list.TotalPages + 1}", GridColors.White);
+ AddLabelHtml(col0X + 20, _main.Rows[2].VCenter + 17, col0Width, 20, $"Total Spawners {_spawners.Length}", GridColors.White, 4, false);
+ }
+
+
+ public void DrawSpawner(ListView list)
+ {
+ for (int i = 0; i < list.Items.Length; i++)
+ {
+ const int vCenter = 15;
+ const int perItem = 45;
+
+ var spawner = _spawners[list.Items[i].Index];
+ var item = list.Items[i];
+
+ AddButton(item.Cols[0].X + 15, list.Items[i].Y + 5, 4011, 4012, GetButtonID(2, item.Index));
+ AddLabelHtml(item.Cols[0].X, list.Items[i].Y + 26, item.Cols[0].Width, 30, "copy", GridColors.White, 3);
+
+ AddButton(item.Cols[1].X + 2, list.Items[i].Y + 5, 4029, 4031, GetButtonID(3, item.Index));
+ AddLabelHtml(item.Cols[1].X + 3, list.Items[i].Y + 26, item.Cols[1].Width, 30, "props", GridColors.White, 3, false);
+
+ AddButton(item.Cols[1].HCenter - 2, list.Items[i].Y + 5, 4029, 4031, GetButtonID(4, item.Index));
+ AddLabelHtml(item.Cols[1].HCenter - 3, list.Items[i].Y + 26, 55, 30, "entry", GridColors.White, 3, false);
+
+ //props
+ AddLabelHtml(item.Cols[2].X, list.Items[i].Y + vCenter + 10, list.Header.Cols[2].Width, 30, spawner.Serial.ToString(), GridColors.White);
+
+ //map
+ AddLabelHtml(item.Cols[3].X, list.Items[i].Y + vCenter, list.Header.Cols[3].Width, 30, spawner.Map.ToString(), GridColors.White);
+
+ //teleport button
+ AddButton(item.Cols[4].X, list.Items[i].Y + vCenter - 4, 0x10, 0x10, GetButtonID(5, item.Index));
+ AddButton(item.Cols[4].X + 20, list.Items[i].Y + vCenter - 4, 0x10, 0x10, GetButtonID(5, item.Index));
+ AddImage(item.Cols[4].X, list.Items[i].Y + vCenter - 5, 0x638, 936);
+ AddImage(item.Cols[4].X + 50, list.Items[i].Y + vCenter - 5, 0x638, 936);
+ AddLabelHtml(item.Cols[4].X, list.Items[i].Y + vCenter, list.Header.Cols[4].Width, 30, ExtractCoords(spawner.Location), GridColors.White);
+
+ //open entry button
+ AddButton(item.Cols[5].X + 6, list.Items[i].Y + vCenter - 4, 0x2635, 0x2635, GetButtonID(7, item.Index));
+ AddImage(item.Cols[5].X + 6, list.Items[i].Y + vCenter - 7, 0x2635, 827);
+ AddLabelHtml(item.Cols[5].X, list.Items[i].Y + vCenter, list.Header.Cols[5].Width, 30, spawner.Entries?.Count.ToString(), GridColors.White);
+
+
+ //entry
+ var entry = i * EntryCount;
+
+ AddImageTiled(item.Cols[2].X - 2, list.Items[i].Y + 18, item.Cols[2].Width, 1, 9357);
+ AddTextEntry(item.Cols[2].X, list.Items[i].Y + 2, list.Header.Cols[2].Width, 80, (int)GridHues.White, entry, spawner.Name, 20);
+ AddButton(item.Cols[2].X, list.Items[i].Y + vCenter + 9, 5837, 5838, GetButtonID(6, item.Index));
+
+ AddImageTiled(item.Cols[6].X + 8, list.Items[i].Y + 29, item.Cols[6].Width / 2, 1, 9357);
+ AddTextEntry(item.Cols[6].X + 12, list.Items[i].Y + 13, list.Header.Cols[6].Width, 80, (int)GridHues.White, entry + 1, spawner.WalkingRange.ToString(), 2);
+
+ AddImageTiled(item.Cols[7].X + 8, list.Items[i].Y + 29, item.Cols[7].Width / 2, 1, 9357);
+ AddTextEntry(item.Cols[7].X + 12, list.Items[i].Y + 13, list.Header.Cols[7].Width, 80, (int)GridHues.White, entry + 2, spawner.HomeRange.ToString(), 2);
+
+ AddImageTiled(item.Cols[8].X + 6, list.Items[i].Y + 29, item.Cols[8].Width - 20, 1, 9357);
+ AddTextEntry(item.Cols[8].X + 8, list.Items[i].Y + 13, list.Header.Cols[8].Width, 80, (int)GridHues.White, entry + 3, spawner.MinDelay.ToString(), 8);
+
+ AddImageTiled(item.Cols[9].X + 6, list.Items[i].Y + 29, item.Cols[9].Width - 20, 1, 9357);
+ AddTextEntry(item.Cols[9].X + 8, list.Items[i].Y + 13, list.Header.Cols[9].Width, 80, (int)GridHues.White, entry + 4, spawner.MaxDelay.ToString(), 8);
+ //end entry
+
+ AddLabelHtml(item.Cols[10].X, list.Items[i].Y + vCenter, list.Header.Cols[10].Width, 30, spawner.NextSpawn.ToString(@"hh\:mm\:ss"), GridColors.White);
+
+ AddButton(item.Cols[11].X, list.Items[i].Y + 5, 4023, 4025, GetButtonID(8, item.Index));
+ AddLabelHtml(item.Cols[11].X + 4, list.Items[i].Y + 26, 55, 30, "save", GridColors.White, 3, false);
+
+ AddButton(item.Cols[11].X + perItem, list.Items[i].Y + 5, 4017, 4018, GetButtonID(9, item.Index));
+ AddLabelHtml(item.Cols[11].X + perItem, list.Items[i].Y + 26, 55, 30, "delete", GridColors.White, 3, false);
+
+ AddImageTiled(0, list.Items[i].Y + list.ColHeight, _main.Width, 1, 1756);
+ AddAlphaRegion(0, list.Items[i].Y + list.ColHeight, _main.Width, 1);
+ }
+ }
+
+ public void DrawColumnName(ListView list)
+ {
+ AddLabelHtml(list.Header.Cols[0].X, list.Header.Y + 10, list.Header.Cols[0].Width, 30, "Copy", GridColors.Gold);
+ AddLabelHtml(list.Header.Cols[1].X, list.Header.Y + 10, list.Header.Cols[1].Width, 30, "Paste", GridColors.Gold);
+ AddLabelHtml(list.Header.Cols[2].X, list.Header.Y + 10, list.Header.Cols[2].Width, 30, "Name/Serial", GridColors.Gold);
+ AddLabelHtml(list.Header.Cols[3].X, list.Header.Y + 10, list.Header.Cols[3].Width, 30, "Map", GridColors.Gold);
+ AddLabelHtml(list.Header.Cols[4].X, list.Header.Y + 10, list.Header.Cols[4].Width, 30, "Coords", GridColors.Gold);
+ AddLabelHtml(list.Header.Cols[5].X, list.Header.Y + 10, list.Header.Cols[5].Width, 30, "Entry", GridColors.Gold);
+ AddLabelHtml(list.Header.Cols[6].X, list.Header.Y + 10, list.Header.Cols[6].Width, 30, "Walk", GridColors.Gold);
+ AddLabelHtml(list.Header.Cols[7].X, list.Header.Y + 10, list.Header.Cols[7].Width, 30, "Home", GridColors.Gold);
+ AddLabelHtml(list.Header.Cols[8].X, list.Header.Y + 10, list.Header.Cols[8].Width, 30, "Min Delay", GridColors.Gold);
+ AddLabelHtml(list.Header.Cols[9].X, list.Header.Y + 10, list.Header.Cols[9].Width, 30, "Max Delay", GridColors.Gold);
+ AddLabelHtml(list.Header.Cols[10].X, list.Header.Y + 10, list.Header.Cols[10].Width, 30, "Next Spawn", GridColors.Gold);
+ AddLabelHtml(list.Header.Cols[11].X, list.Header.Y + 10, list.Header.Cols[11].Width, 30, "Actions", GridColors.Gold);
+ }
+
+ public SpawnerControllerGump(Mobile mobile, int page = 0, BaseSpawner copy = null, SpawnSearch search = null) : base(20, 30)
+ {
+ _main = Grid("main", 1000, 800, 1, 3, rowSize: "10* * 100");
+ _mobile = mobile;
+ _page = page;
+ _copy = copy;
+ _search = search ?? new SpawnSearch();
+
+ SearchSpawner();
+
+ var _list = AddListView(
+ _main.Name,
+ 0,
+ 1,
+ _spawners.Length,
+ page,
+ 45,
+ 12,
+ colSize: "6* 8* 18* 6* 12* 6* 5* 5* 8* 8* 9* *",
+ headerHeight: 30,
+ marginY: -7
+ );
+
+ _lineCount = _list.LineCount;
+ DrawBorder(_list);
+ DrawSearch();
+ DrawSpawner(_list);
+ DrawColumnName(_list);
+ DrawFooter(_list);
+
+ }
+ public void CopyProperty(BaseSpawner spawner, BaseSpawner target)
+ {
+ target.Name = spawner.Name;
+ target.MinDelay = spawner.MinDelay;
+ target.MaxDelay = spawner.MaxDelay;
+ target.WalkingRange = spawner.WalkingRange;
+ target.HomeRange = spawner.HomeRange;
+ target.Group = spawner.Group;
+ target.Count = spawner.Count;
+ }
+ public void CopyEntry(BaseSpawner spawner, BaseSpawner target)
+ {
+ if (spawner.Entries?.Count > 0)
+ {
+ target.Entries?.Clear();
+
+ for (int i = 0; i < spawner.Entries.Count; i++)
+ {
+ var item = spawner.Entries[i];
+ var targetEntry = target.AddEntry(item.SpawnedName, item.SpawnedProbability, item.SpawnedMaxCount);
+ targetEntry.Properties = item.Properties;
+ targetEntry.Parameters = item.Parameters;
+ }
+
+ }
+ }
+ public void FullCopy(BaseSpawner spawner, BaseSpawner target)
+ {
+ CopyProperty(spawner, target);
+ CopyEntry(spawner, target);
+ }
+ public void Refresh(bool update = true)
+ {
+ if (update)
+ {
+ _mobile.SendGump(new SpawnerControllerGump(_mobile, _page, _copy, _search));
+ }
+ else
+ {
+ _mobile.SendGump(this);
+ }
+ }
+
+ public override void OnResponse(NetState sender, RelayInfo info)
+ {
+ var buttonID = info.ButtonID - 1;
+ var type = buttonID % TypeCount;
+ var index = buttonID / TypeCount;
+
+ switch (type)
+ {
+ case -1:
+ {
+ return;
+ }
+ case 1:
+ {
+ _page = 0;
+ if (index < 4)
+ {
+ _search.Type = (SpawnSearchType)index;
+ var isNumeric = int.TryParse(_search.SearchPattern, out _);
+ if (index == 1 && !isNumeric || index != 1 && isNumeric)
+ {
+ _search.SearchPattern = "";
+ }
+ }
+ else if (index == 4)
+ {
+ var entry = info.GetTextEntry(0xFFFF);
+
+ if (entry?.Text?.Length > 0)
+ {
+ _search.SearchPattern = entry.Text;
+ }
+ }
+ else if (index == 5 && _copy != null && _spawners?.Length > 0)
+ {
+ foreach (var spawner in _spawners)
+ {
+ CopyProperty(_copy, spawner);
+ }
+ }
+ else if (index == 6 && _copy != null && _spawners?.Length > 0)
+ {
+ foreach (var spawner in _spawners)
+ {
+ CopyEntry(_copy, spawner);
+ }
+ }
+ else if (index == 7 && _copy != null)
+ {
+ var newSpawner = new Spawner();
+
+ FullCopy(_copy, newSpawner);
+ newSpawner.Map = _mobile.Map;
+ newSpawner.Location = _mobile.Location;
+ newSpawner.Stop();
+ newSpawner.Start();
+ newSpawner.Respawn();
+ }
+
+ break;
+ }
+ //copy
+ case 2:
+ {
+ if (index < _spawners.Length)
+ {
+ _copy = _spawners[index];
+ }
+
+ break;
+ }
+ //paste props
+ case 3:
+ {
+ if (index < _spawners.Length && _copy != null)
+ {
+ CopyProperty(_copy, _spawners[index]);
+ }
+
+ break;
+ }
+ //paste entry
+ case 4:
+ {
+ if (index < _spawners.Length && _copy != null)
+ {
+ CopyEntry(_copy, _spawners[index]);
+ }
+
+ break;
+ }
+ //save
+ case 8:
+ {
+ if (index < _spawners.Length)
+ {
+ var spawner = _spawners[index];
+
+ var indexEntry = index >= _lineCount ? (index - _lineCount * _page) * EntryCount : index * EntryCount;
+
+ var name = info.GetTextEntry(indexEntry);
+ if (name?.Text.Length > 0)
+ {
+ spawner.Name = name.Text;
+ }
+
+ if (int.TryParse(info.GetTextEntry(indexEntry + 1)?.Text, out var walkRange))
+ {
+ spawner.WalkingRange = walkRange;
+ }
+
+ if (int.TryParse(info.GetTextEntry(indexEntry + 2)?.Text, out var homeHange))
+ {
+ spawner.HomeRange = homeHange;
+ }
+
+ if (TimeSpan.TryParse(info.GetTextEntry(indexEntry + 3)?.Text, out var minDelay))
+ {
+ spawner.MinDelay = minDelay;
+ }
+
+ if (TimeSpan.TryParse(info.GetTextEntry(indexEntry + 4)?.Text, out var maxDelay))
+ {
+ spawner.MaxDelay = maxDelay;
+ }
+ }
+
+ break;
+ }
+ //go
+ case 5:
+ {
+ if (index < _spawners.Length)
+ {
+ var spawner = _spawners[index];
+ _mobile.Map = spawner.Map;
+ _mobile.Location = spawner.Location;
+ }
+ Refresh(false);
+ return;
+ }
+ //open entry
+ case 6:
+ case 7:
+ {
+ if (index < _spawners.Length)
+ {
+ var spawner = _spawners[index];
+ Refresh();
+
+ if (type == 7)
+ {
+ _mobile.SendGump(new SpawnerGump(spawner));
+ }
+ else
+ {
+ _mobile.SendGump(new PropertiesGump(_mobile, spawner));
+ }
+
+ return;
+ }
+
+ break;
+ }
+ //delete
+ case 9:
+ {
+ if (index < _spawners.Length)
+ {
+ _spawners[index].Delete();
+ }
+
+ break;
+ }
+ case 10:
+ {
+ _page++;
+ break;
+ }
+ case 11:
+ {
+ if (_page > 0)
+ {
+ _page--;
+ }
+
+ break;
+ }
+ }
+
+ Refresh();
+ }
+}
diff --git a/Projects/UOContent/Gumps/BaseGridGump.cs b/Projects/UOContent/Gumps/BaseGridGump.cs
index c755cde62..c5d69ec15 100644
--- a/Projects/UOContent/Gumps/BaseGridGump.cs
+++ b/Projects/UOContent/Gumps/BaseGridGump.cs
@@ -1,3 +1,6 @@
+using System.Runtime.CompilerServices;
+using Server.Utilities;
+
namespace Server.Gumps
{
public abstract class BaseGridGump : Gump
@@ -37,9 +40,10 @@ namespace Server.Gumps
public virtual int TextHue => 0;
public virtual int TextOffsetX => 2;
- public string Center(string text) => $"{text}";
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public string Center(string text) => text.Center();
- public string Color(string text, int color) => $"{text}";
+ public string Color(string text, int color) => text.Color(color);
public int GetButtonID(int typeCount, int type, int index) => 1 + index * typeCount + type;