modernuo/Projects/UOContent/Commands/AddonGenerator.cs
Kamron Batman 8282b00ca2
feat: Moves gumps out of the core (#1916)
> [!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.
2024-08-09 19:07:32 -07:00

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)
);
}
}
}