mirror of
https://github.com/modernuo/ModernUO
synced 2026-08-11 22:23:06 -04:00
> [!Important] > **Developer Note** > This code change will **completely move gumps out of the core** ### Summary - Adds `GetGumps()` convenience which exposes methods to Find/Close/Send multiple gumps. This helper is a performance improvement by eliminating the Dictionary<Player, List> lookup for gumps.
410 lines
12 KiB
C#
410 lines
12 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using Server.Collections;
|
|
using Server.Items;
|
|
using Server.Gumps;
|
|
using Server.Text;
|
|
|
|
namespace Server.Commands;
|
|
|
|
public class AddonGenerator
|
|
{
|
|
private static readonly string _outputDirectory = Path.Combine(Core.BaseDirectory, "Data", "Generated Addons");
|
|
|
|
public delegate void PickerCallbackDelegate(Mobile from, Map map, Point3D start, Point3D end, object state);
|
|
|
|
public static void PickerCallbackWrapper(Mobile from, Map map, Point3D start, Point3D end, PickerCallbackDelegate callback, object state)
|
|
{
|
|
callback(from, map, start, end, state);
|
|
}
|
|
|
|
private const string _template =
|
|
"""
|
|
/////////////////////////////////////////////////
|
|
//
|
|
// Automatically generated by the
|
|
// AddonGenerator script for ModernUO
|
|
//
|
|
/////////////////////////////////////////////////
|
|
using System;
|
|
using Server;{serverItemsNamespace}
|
|
|
|
namespace {namespace};
|
|
|
|
[SerializationGenerator(0)]
|
|
public class {name}Addon : BaseAddon
|
|
{
|
|
public override BaseAddonDeed Deed => {name}AddonDeed();
|
|
|
|
[Constructible]
|
|
public {name}Addon()
|
|
{
|
|
{components}
|
|
}
|
|
}
|
|
|
|
[SerializationGenerator(0)]
|
|
public class {name}AddonDeed : BaseAddonDeed
|
|
{
|
|
public override BaseAddon Addon => new {name}Addon();
|
|
public override string DefaultName => "{name}";
|
|
|
|
[Constructible]
|
|
public {name}AddonDeed()
|
|
{
|
|
}
|
|
}
|
|
""";
|
|
|
|
// TODO: .NET 9 - Use search values
|
|
// private static SearchValues<string> _templatePlaceholders = SearchValues.Create("{serverItemsNamespace}", "{namespace}", "{name}", "{components}");
|
|
|
|
public static void Configure()
|
|
{
|
|
CommandSystem.Register("AddonGen", AccessLevel.Administrator, OnAddonGen);
|
|
}
|
|
|
|
[Usage("AddonGen [<name> [namespace]]")]
|
|
[Description("Brings up the addon script generator gump. Specify a name to skip the gump and go directly to picking an area.")]
|
|
private static void OnAddonGen(CommandEventArgs e)
|
|
{
|
|
var pickerState = new PickerState
|
|
{
|
|
Name = "",
|
|
Namespace = "Server.Items",
|
|
Items = true,
|
|
Statics = true,
|
|
Range = false,
|
|
Min = sbyte.MinValue,
|
|
Max = sbyte.MaxValue
|
|
};
|
|
|
|
if (e.Arguments.Length <= 0)
|
|
{
|
|
// Send gump
|
|
e.Mobile.SendGump(new InternalGump(e.Mobile, pickerState));
|
|
return;
|
|
}
|
|
|
|
pickerState.Name = e.Arguments[0];
|
|
pickerState.Namespace = e.Arguments.Length > 1 ? e.Arguments[1] : "Server.Items";
|
|
|
|
BoundingBoxPicker.Begin(
|
|
e.Mobile,
|
|
(map, start, end) => PickerCallbackWrapper(e.Mobile, map, start, end, PickerCallback, pickerState)
|
|
);
|
|
}
|
|
|
|
private static void PickerCallback(Mobile from, Map map, Point3D start, Point3D end, object state)
|
|
{
|
|
if (state is not PickerState pickerState)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (start.X > end.X)
|
|
{
|
|
(start.X, end.X) = (end.X, start.X);
|
|
}
|
|
|
|
if (start.Y > end.Y)
|
|
{
|
|
(start.Y, end.Y) = (end.Y, start.Y);
|
|
}
|
|
|
|
Point2D startPoint2D = new Point2D(start.X, start.Y);
|
|
Point2D endPoint2D = new Point2D(end.X, end.Y);
|
|
Rectangle2D bounds = new Rectangle2D(startPoint2D, endPoint2D);
|
|
|
|
string name = pickerState.Name;
|
|
string ns = pickerState.Namespace;
|
|
bool items = pickerState.Items;
|
|
bool statics = pickerState.Statics;
|
|
bool range = pickerState.Range;
|
|
sbyte min = pickerState.Min;
|
|
sbyte max = pickerState.Max;
|
|
|
|
// Get center
|
|
Point3D center = new Point3D();
|
|
center.Z = 127;
|
|
|
|
int x1 = bounds.End.X;
|
|
int y1 = bounds.End.Y;
|
|
int x2 = bounds.Start.X;
|
|
int y2 = bounds.Start.Y;
|
|
|
|
Dictionary<Point2D, List<StaticTile>> tiles = new Dictionary<Point2D, List<StaticTile>>();
|
|
|
|
if (statics)
|
|
{
|
|
for (int x = start.X; x <= end.X; x++)
|
|
{
|
|
for (int y = start.Y; y <= end.Y; y++)
|
|
{
|
|
TileMatrix tileMatrix = map.Tiles;
|
|
List<StaticTile> list = [];
|
|
|
|
if (items)
|
|
{
|
|
foreach (Item item in map.GetItemsAt(x, y))
|
|
{
|
|
if (range && item.Z >= min && item.Z <= max)
|
|
{
|
|
list.Add(new StaticTile((ushort)item.ItemID, (sbyte)item.Z));
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var staticTile in tileMatrix.GetStaticTiles(x, y))
|
|
{
|
|
if (range && staticTile.Z >= min && staticTile.Z <= max)
|
|
{
|
|
list.Add(staticTile);
|
|
}
|
|
}
|
|
|
|
if (list is { Count: > 0 })
|
|
{
|
|
tiles[new Point2D(x, y)] = list;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
using var target = PooledRefList<Item>.Create();
|
|
|
|
foreach (Static s in map.GetItemsInBounds<Static>(bounds))
|
|
{
|
|
if (!range || s.Z >= min && s.Z <= max)
|
|
{
|
|
target.Add(s);
|
|
}
|
|
}
|
|
|
|
if (target.Count == 0 && tiles.Keys.Count == 0)
|
|
{
|
|
from.SendMessage(0x40, "No items have been selected");
|
|
return;
|
|
}
|
|
|
|
// Get correct bounds
|
|
foreach (Item item in target)
|
|
{
|
|
if (item.Z < center.Z)
|
|
{
|
|
center.Z = item.Z;
|
|
}
|
|
|
|
x1 = Math.Min(x1, item.X);
|
|
y1 = Math.Min(y1, item.Y);
|
|
x2 = Math.Max(x2, item.X);
|
|
y2 = Math.Max(y2, item.Y);
|
|
}
|
|
|
|
foreach (Point2D p in tiles.Keys)
|
|
{
|
|
List<StaticTile> list = tiles[p];
|
|
|
|
foreach (StaticTile t in list)
|
|
{
|
|
if (t.Z < center.Z)
|
|
{
|
|
center.Z = t.Z;
|
|
}
|
|
}
|
|
|
|
x1 = Math.Min(x1, p.X);
|
|
y1 = Math.Min(y1, p.Y);
|
|
x2 = Math.Max(x2, p.X);
|
|
y2 = Math.Max(y2, p.Y);
|
|
}
|
|
|
|
center.X = x1 + (x2 - x1) / 2;
|
|
center.Y = y1 + (y2 - y1) / 2;
|
|
|
|
using var sb = ValueStringBuilder.Create();
|
|
|
|
foreach (Point2D p in tiles.Keys)
|
|
{
|
|
List<StaticTile> list = tiles[p];
|
|
|
|
int xOffset = p.X - center.X;
|
|
int yOffset = p.Y - center.Y;
|
|
|
|
foreach (StaticTile t in list)
|
|
{
|
|
int zOffset = t.Z - center.Z;
|
|
int id = t.ID - 16384;
|
|
|
|
sb.Append($" AddComponent(new AddonComponent({id}), {xOffset}, {yOffset}, {zOffset});\n");
|
|
}
|
|
}
|
|
|
|
foreach (Item item in target)
|
|
{
|
|
int xOffset = item.X - center.X;
|
|
int yOffset = item.Y - center.Y;
|
|
int zOffset = item.Z - center.Z;
|
|
|
|
var light = (item.ItemData.Flags & TileFlag.LightSource) == TileFlag.LightSource ? item.Light : LightType.Empty;
|
|
var hue = item.Hue;
|
|
|
|
sb.Append(" AddComponent(\n");
|
|
if (hue != 0 || light != LightType.Empty)
|
|
{
|
|
sb.Append($" new AddonComponent({item.ItemID})\n");
|
|
sb.Append(" {\n");
|
|
if (light != LightType.Empty)
|
|
{
|
|
sb.Append($" Light = LightType.{light},\n");
|
|
}
|
|
if (hue != 0)
|
|
{
|
|
sb.Append($" Hue = {hue},\n");
|
|
}
|
|
sb.Append(" },\n");
|
|
}
|
|
else
|
|
{
|
|
sb.Append($" new AddonComponent({item.ItemID}),\n");
|
|
}
|
|
sb.Append($" {xOffset},\n");
|
|
sb.Append($" {yOffset},\n");
|
|
sb.Append($" {zOffset},\n");
|
|
sb.Append(" )\n");
|
|
}
|
|
|
|
string output = _template.Replace("{name}", name);
|
|
output = output.Replace("{namespace}", ns);
|
|
output = output.Replace("{components}", sb.ToString());
|
|
|
|
var path = Path.Combine(_outputDirectory, $"{name}Addon.cs");
|
|
|
|
string folder = Path.GetDirectoryName(path);
|
|
PathUtility.EnsureDirectory(folder);
|
|
|
|
using var writer = new StreamWriter(path, false);
|
|
writer.Write(output);
|
|
|
|
from.SendMessage(0x40, $"Script saved to {path}");
|
|
}
|
|
|
|
private class PickerState
|
|
{
|
|
public string Name { get; set; }
|
|
public string Namespace { get; set; }
|
|
public bool Items { get; set; }
|
|
public bool Statics { get; set; }
|
|
public bool Range { get; set; }
|
|
public sbyte Min { get; set; }
|
|
public sbyte Max { get; set; }
|
|
}
|
|
|
|
private class InternalGump : Gump
|
|
{
|
|
private const int LabelHue = 0x480;
|
|
private const int GreenHue = 0x40;
|
|
private readonly PickerState _state;
|
|
|
|
public override bool Singleton => true;
|
|
|
|
public InternalGump(Mobile m, PickerState state) : base(100, 50)
|
|
{
|
|
_state = state;
|
|
MakeGump();
|
|
}
|
|
|
|
private void MakeGump()
|
|
{
|
|
Closable = true;
|
|
Disposable = true;
|
|
Draggable = true;
|
|
Resizable = false;
|
|
AddPage(0);
|
|
AddBackground(0, 0, 280, 225, 9270);
|
|
AddAlphaRegion(10, 10, 260, 205);
|
|
AddLabel(64, 15, GreenHue, "Addon Script Generator");
|
|
|
|
AddLabel(20, 40, LabelHue, "Name");
|
|
AddImageTiled(95, 55, 165, 1, 9304);
|
|
AddTextEntry(95, 35, 165, 20, LabelHue, 0, _state.Name);
|
|
|
|
AddLabel(20, 60, LabelHue, "Namespace");
|
|
AddImageTiled(95, 75, 165, 1, 9304);
|
|
AddTextEntry(95, 55, 165, 20, LabelHue, 1, _state.Namespace);
|
|
|
|
AddCheck(20, 85, 2510, 2511, _state.Items, 0);
|
|
AddLabel(40, 85, LabelHue, "Export Items");
|
|
|
|
AddCheck(20, 110, 2510, 2511, _state.Statics, 1);
|
|
AddLabel(40, 110, LabelHue, "Export Statics");
|
|
|
|
AddCheck(20, 135, 2510, 2511, _state.Range, 2);
|
|
AddLabel(40, 135, LabelHue, "Specify Z Range");
|
|
|
|
AddLabel(50, 160, LabelHue, "Max");
|
|
AddImageTiled(85, 175, 60, 1, 9304);
|
|
AddTextEntry(85, 155, 60, 20, LabelHue, 2, _state.Min.ToString());
|
|
|
|
AddLabel(160, 160, LabelHue, "Min");
|
|
AddImageTiled(200, 175, 60, 1, 9304);
|
|
AddTextEntry(200, 155, 60, 20, LabelHue, 3, _state.Max.ToString());
|
|
|
|
AddButton(20, 185, 4020, 4021, 0);
|
|
AddLabel(55, 185, LabelHue, "Cancel");
|
|
|
|
AddButton(155, 185, 4005, 4006, 1);
|
|
AddLabel(195, 185, LabelHue, "Generate");
|
|
}
|
|
|
|
public override void OnResponse(Network.NetState sender, in RelayInfo info)
|
|
{
|
|
if (info.ButtonID == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_state.Name = info.GetTextEntry(0);
|
|
_state.Namespace = info.GetTextEntry(1);
|
|
|
|
_state.Items = info.IsSwitched(0);
|
|
_state.Statics = info.IsSwitched(1);
|
|
_state.Range = info.IsSwitched(2);
|
|
|
|
var fail = !_state.Items && !_state.Statics;
|
|
|
|
if (!sbyte.TryParse(info.GetTextEntry(2), out sbyte min))
|
|
{
|
|
min = sbyte.MinValue;
|
|
fail = true;
|
|
}
|
|
|
|
if (!sbyte.TryParse(info.GetTextEntry(3), out sbyte max))
|
|
{
|
|
max = sbyte.MaxValue;
|
|
fail = true;
|
|
}
|
|
|
|
if (max < min)
|
|
{
|
|
(max, min) = (min, max);
|
|
}
|
|
|
|
_state.Min = min;
|
|
_state.Max = max;
|
|
|
|
if (fail)
|
|
{
|
|
sender.Mobile.SendMessage(0x40, "Please review the generation parameters, some are invalid.");
|
|
sender.Mobile.SendGump(new InternalGump(sender.Mobile, _state));
|
|
return;
|
|
}
|
|
|
|
BoundingBoxPicker.Begin(
|
|
sender.Mobile,
|
|
(map, start, end) => PickerCallbackWrapper(sender.Mobile, map, start, end, PickerCallback, _state)
|
|
);
|
|
}
|
|
}
|
|
}
|