using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using Server.Gumps; using Server.Items; using Server.Text; using static Server.Attributes; using static Server.Types; namespace Server.Commands; public static class Add { public static void Configure() { CommandSystem.Register("Tile", AccessLevel.GameMaster, Tile_OnCommand); CommandSystem.Register("TileRXYZ", AccessLevel.GameMaster, TileRXYZ_OnCommand); CommandSystem.Register("TileXYZ", AccessLevel.GameMaster, TileXYZ_OnCommand); CommandSystem.Register("TileZ", AccessLevel.GameMaster, TileZ_OnCommand); CommandSystem.Register("TileAvg", AccessLevel.GameMaster, TileAvg_OnCommand); CommandSystem.Register("Outline", AccessLevel.GameMaster, Outline_OnCommand); CommandSystem.Register("OutlineRXYZ", AccessLevel.GameMaster, OutlineRXYZ_OnCommand); CommandSystem.Register("OutlineXYZ", AccessLevel.GameMaster, OutlineXYZ_OnCommand); CommandSystem.Register("OutlineZ", AccessLevel.GameMaster, OutlineZ_OnCommand); CommandSystem.Register("OutlineAvg", AccessLevel.GameMaster, OutlineAvg_OnCommand); } public static void Invoke( Mobile from, Point3D start, Point3D end, string[] args, List packs = null, bool outline = false, bool mapAvg = false ) { var type = AddGump.ExactMatch(args[0]); if (type == null) { from.SendMessage("No type with that name was found."); return; } Invoke(from, start, end, type, args.AsSpan(1), packs, outline, mapAvg); } public static void Invoke(Mobile from, Point3D start, Point3D end, ConstructorInfo ctor) { using var sb = ValueStringBuilder.Create(); sb.Append($"{from.AccessLevel} {CommandLogging.Format(from)} building "); if (start == end) { sb.Append($"at {start} in {from.Map}"); } else { sb.Append($"from {start} to {end} in {from.Map}"); } sb.Append($": {ctor.DeclaringType!.Name}"); CommandLogging.WriteLine(from, sb.ToString()); var watch = new Stopwatch(); watch.Start(); Utility.FixPoints(ref start, ref end); // Handle optional parameters var paramList = ctor.GetParameters(); var parms = paramList.Length == 0 ? Array.Empty() : new object[paramList.Length]; for (var i = 0; i < parms.Length; i++) { parms[i] = Type.Missing; } var built = Build(from, start, end, ctor, parms, null, null, null); if (built <= 0) { SendUsage(ctor, from); return; } watch.Stop(); if (built == 1) { from.SendMessage($"{built} object generated in {watch.Elapsed.TotalSeconds:F2} seconds."); } else { from.SendMessage($"{built} objects generated in {watch.Elapsed.TotalSeconds:F2} seconds."); } } public static void Invoke( Mobile from, Point3D start, Point3D end, Type type, ReadOnlySpan args = default, List packs = null, bool outline = false, bool mapAvg = false ) { using var sb = ValueStringBuilder.Create(); sb.Append($"{from.AccessLevel} {CommandLogging.Format(from)} building "); if (start == end) { sb.Append($"at {start} in {from.Map}"); } else { sb.Append($"from {start} to {end} in {from.Map}"); } sb.Append($": {type.Name}"); string[,] props = null; var setArgsIndex = -1; for (var i = 0; i < args.Length; ++i) { sb.Append($" \"{args[i]}\""); if (setArgsIndex != -1 || !args[i].InsensitiveEquals("set")) { continue; } setArgsIndex = i; var remains = args.Length - i - 1; if (remains < 2) { continue; } props = new string[remains / 2, 2]; remains /= 2; for (var j = 0; j < remains; ++j) { props[j, 0] = args[i + j * 2 + 1]; props[j, 1] = args[i + j * 2 + 2]; } } CommandLogging.WriteLine(from, sb.ToString()); ReadOnlySpan ctorArgs = setArgsIndex != -1 ? args[..setArgsIndex] : args; var watch = new Stopwatch(); watch.Start(); var built = BuildObjects(from, type, start, end, ctorArgs, props, packs, outline, mapAvg); if (built <= 0) { SendUsage(type, from); return; } watch.Stop(); if (built == 1) { from.SendMessage($"{built} object generated in {watch.Elapsed.TotalSeconds:F2} seconds."); } else { from.SendMessage($"{built} objects generated in {watch.Elapsed.TotalSeconds:F2} seconds."); } } public static int BuildObjects( Mobile from, Type type, Point3D start, Point3D end, ReadOnlySpan args, string[,] props, List packs, bool outline = false, bool mapAvg = false ) { Utility.FixPoints(ref start, ref end); PropertyInfo[] realProps = null; if (props != null) { realProps = new PropertyInfo[props.GetLength(0)]; var allProps = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public); for (var i = 0; i < realProps.Length; ++i) { var propName = props[i, 0]; var thisProp = Properties.GetPropertyInfoByName(from, allProps, propName, PropertyAccess.Write, out var failReason); if (failReason == null) { realProps[i] = thisProp; } else { from.SendMessage(failReason); } } } var ctors = type.GetConstructors(); for (var i = 0; i < ctors.Length; ++i) { var ctor = ctors[i]; if (!IsConstructible(ctor, from.AccessLevel)) { continue; } // Handle optional constructors var paramList = ctor.GetParameters(); var totalParams = 0; for (var j = 0; j < paramList.Length; j++) { if (!paramList[j].HasDefaultValue) { totalParams++; } } if (args.Length >= totalParams && args.Length <= paramList.Length) { var paramValues = ParseValues(paramList, args); if (paramValues == null) { continue; } var built = Build(from, start, end, ctor, paramValues, props, realProps, packs, outline, mapAvg); if (built > 0) { return built; } } } return 0; } public static object[] ParseValues(ParameterInfo[] paramList, ReadOnlySpan args) { var values = new object[paramList.Length]; for (int i = 0, a = 0; i < paramList.Length; i++) { var param = paramList[i]; TryParse(param.ParameterType, a < args.Length ? args[a++] : null, out var value); if (value != null) { values[i] = value; } else if (param.HasDefaultValue) { values[i] = Type.Missing; } else { return null; } } return values; } public static IEntity Build( Mobile from, ConstructorInfo ctor, object[] values, string[,] props, PropertyInfo[] realProps, ref bool sendError ) { var built = ctor.Invoke(values); if (realProps != null) { var hadError = false; for (var i = 0; i < realProps.Length; ++i) { if (realProps[i] == null) { continue; } var result = Properties.InternalSetValue(from, built, built, realProps[i], props[i, 1], props[i, 1], false); if (result != "Property has been set.") { if (sendError) { from.SendMessage(result); } hadError = true; } } if (hadError) { sendError = false; } } return (IEntity)built; } public static int Build( Mobile from, Point3D start, Point3D end, ConstructorInfo ctor, object[] values, string[,] props, PropertyInfo[] realProps, List packs, bool outline = false, bool mapAvg = false ) { try { var map = from.Map; var width = end.X - start.X + 1; var height = end.Y - start.Y + 1; if (outline && (width < 3 || height < 3)) { outline = false; } int objectCount; if (packs != null) { objectCount = packs.Count; } else if (outline) { objectCount = (width + height - 2) * 2; } else { objectCount = width * height; } if (objectCount >= 20) { from.SendMessage($"Constructing {objectCount} objects, please wait."); } var sendError = true; using var sb = ValueStringBuilder.Create(); sb.Append("Serials: "); if (packs != null) { for (var i = 0; i < packs.Count; ++i) { var built = Build(from, ctor, values, props, realProps, ref sendError); sb.Append($"{built.Serial}; "); if (built is Item item) { packs[i].DropItem(item); } else if (built is Mobile m) { m.MoveToWorld(new Point3D(start.X, start.Y, start.Z), map); } } } else { var z = start.Z; for (var x = start.X; x <= end.X; ++x) { for (var y = start.Y; y <= end.Y; ++y) { if (outline && x != start.X && x != end.X && y != start.Y && y != end.Y) { continue; } if (mapAvg) { z = map.GetAverageZ(x, y); } var built = Build(from, ctor, values, props, realProps, ref sendError); sb.Append($"{built.Serial}; "); if (built is Item item) { item.MoveToWorld(new Point3D(x, y, z), map); } else if (built is Mobile m) { m.MoveToWorld(new Point3D(x, y, z), map); } } } } CommandLogging.WriteLine(from, sb.ToString()); return objectCount; } catch (Exception ex) { Console.WriteLine(ex); return 0; } } public static void SendUsage(Type type, Mobile from) { var ctors = type.GetConstructors(); var foundCtor = false; for (var i = 0; i < ctors.Length; ++i) { var ctor = ctors[i]; if (!IsConstructible(ctor, from.AccessLevel)) { continue; } if (!foundCtor) { foundCtor = true; from.SendMessage("Usage:"); } SendCtor(ctor, from); } if (!foundCtor) { from.SendMessage("That type is not marked constructible."); } } public static void SendUsage(ConstructorInfo ctor, Mobile from) { if (!IsConstructible(ctor, from.AccessLevel)) { from.SendMessage("That type is not marked constructible."); return; } from.SendMessage("Usage:"); SendCtor(ctor, from); } public static void SendCtor(ConstructorInfo ctor, Mobile from) { var paramList = ctor.GetParameters(); using var sb = ValueStringBuilder.Create(); sb.Append(ctor.DeclaringType!.Name); for (var i = 0; i < paramList.Length; ++i) { if (i != 0) { sb.Append(','); } sb.Append(' '); sb.Append(paramList[i].ParameterType.Name); sb.Append(' '); sb.Append(paramList[i].Name); } from.SendMessage(sb.ToString()); } private static void TileBox_Callback(Mobile from, Point3D start, Point3D end, TileState ts) { var mapAvg = false; switch (ts.m_ZType) { case TileZType.Fixed: { start.Z = end.Z = ts.m_FixedZ; break; } case TileZType.MapAverage: { mapAvg = true; break; } } Invoke(from, start, end, ts.m_Args, null, ts.m_Outline, mapAvg); } private static void Internal_OnCommand(CommandEventArgs e, bool outline) { var from = e.Mobile; if (e.Length >= 1) { BoundingBoxPicker.Begin( from, (_, start, end) => TileBox_Callback(from, start, end, new TileState(TileZType.Start, 0, e.Arguments, outline)) ); } else { if (outline) { from.SendMessage("Format: Outline [params] [set { ...}]"); } else { from.SendMessage("Format: Tile [params] [set { ...}]"); } } } private static void InternalRXYZ_OnCommand(CommandEventArgs e, bool outline) { if (e.Length >= 6) { var p = new Point3D(e.Mobile.X + e.GetInt32(0), e.Mobile.Y + e.GetInt32(1), e.Mobile.Z + e.GetInt32(4)); var p2 = new Point3D(p.X + e.GetInt32(2) - 1, p.Y + e.GetInt32(3) - 1, p.Z); var subArgs = new string[e.Length - 5]; for (var i = 0; i < subArgs.Length; ++i) { subArgs[i] = e.Arguments[i + 5]; } Invoke(e.Mobile, p, p2, subArgs, null, outline); } else { if (outline) { e.Mobile.SendMessage("Format: OutlineRXYZ [params] [set { ...}]"); } else { e.Mobile.SendMessage("Format: TileRXYZ [params] [set { ...}]"); } } } private static void InternalXYZ_OnCommand(CommandEventArgs e, bool outline) { if (e.Length >= 6) { var p = new Point3D(e.GetInt32(0), e.GetInt32(1), e.GetInt32(4)); var p2 = new Point3D(p.X + e.GetInt32(2) - 1, p.Y + e.GetInt32(3) - 1, e.GetInt32(4)); var subArgs = new string[e.Length - 5]; for (var i = 0; i < subArgs.Length; ++i) { subArgs[i] = e.Arguments[i + 5]; } Invoke(e.Mobile, p, p2, subArgs, null, outline); } else { if (outline) { e.Mobile.SendMessage("Format: OutlineXYZ [params] [set { ...}]" ); } else { e.Mobile.SendMessage("Format: TileXYZ [params] [set { ...}]"); } } } private static void InternalZ_OnCommand(CommandEventArgs e, bool outline) { var from = e.Mobile; if (e.Length >= 2) { var subArgs = new string[e.Length - 1]; for (var i = 0; i < subArgs.Length; ++i) { subArgs[i] = e.Arguments[i + 1]; } BoundingBoxPicker.Begin( from, (_, start, end) => TileBox_Callback(from, start, end, new TileState(TileZType.Fixed, e.GetInt32(0), subArgs, outline)) ); } else { if (outline) { from.SendMessage("Format: OutlineZ [params] [set { ...}]"); } else { from.SendMessage("Format: TileZ [params] [set { ...}]"); } } } private static void InternalAvg_OnCommand(CommandEventArgs e, bool outline) { var from = e.Mobile; if (e.Length >= 1) { BoundingBoxPicker.Begin( from, (_, start, end) => TileBox_Callback(from, start, end, new TileState(TileZType.MapAverage, 0, e.Arguments, outline)) ); } else { if (outline) { from.SendMessage("Format: OutlineAvg [params] [set { ...}]"); } else { from.SendMessage("Format: TileAvg [params] [set { ...}]"); } } } [Usage("Tile [params] [set { ...}]")] [Description( "Tiles an item or npc by name into a targeted bounding box. Optional constructor parameters. Optional set property list." )] public static void Tile_OnCommand(CommandEventArgs e) { Internal_OnCommand(e, false); } [Usage("TileRXYZ [params] [set { ...}]")] [Description( "Tiles an item or npc by name into a given bounding box, (x, y) parameters are relative to your characters position. Optional constructor parameters. Optional set property list." )] public static void TileRXYZ_OnCommand(CommandEventArgs e) { InternalRXYZ_OnCommand(e, false); } [Usage("TileXYZ [params] [set { ...}]")] [Description( "Tiles an item or npc by name into a given bounding box. Optional constructor parameters. Optional set property list." )] public static void TileXYZ_OnCommand(CommandEventArgs e) { InternalXYZ_OnCommand(e, false); } [Usage("TileZ [params] [set { ...}]")] [Description( "Tiles an item or npc by name into a targeted bounding box at a fixed Z location. Optional constructor parameters. Optional set property list." )] public static void TileZ_OnCommand(CommandEventArgs e) { InternalZ_OnCommand(e, false); } [Usage("TileAvg [params] [set { ...}]")] [Description( "Tiles an item or npc by name into a targeted bounding box on the map's average Z elevation. Optional constructor parameters. Optional set property list." )] public static void TileAvg_OnCommand(CommandEventArgs e) { InternalAvg_OnCommand(e, false); } [Usage("Outline [params] [set { ...}]")] [Description( "Tiles an item or npc by name around a targeted bounding box. Optional constructor parameters. Optional set property list." )] public static void Outline_OnCommand(CommandEventArgs e) { Internal_OnCommand(e, true); } [Usage("OutlineRXYZ [params] [set { ...}]")] [Description( "Tiles an item or npc by name around a given bounding box, (x, y) parameters are relative to your characters position. Optional constructor parameters. Optional set property list." )] public static void OutlineRXYZ_OnCommand(CommandEventArgs e) { InternalRXYZ_OnCommand(e, true); } [Usage("OutlineXYZ [params] [set { ...}]")] [Description( "Tiles an item or npc by name around a given bounding box. Optional constructor parameters. Optional set property list." )] public static void OutlineXYZ_OnCommand(CommandEventArgs e) { InternalXYZ_OnCommand(e, true); } [Usage("OutlineZ [params] [set { ...}]")] [Description( "Tiles an item or npc by name around a targeted bounding box at a fixed Z location. Optional constructor parameters. Optional set property list." )] public static void OutlineZ_OnCommand(CommandEventArgs e) { InternalZ_OnCommand(e, true); } [Usage("OutlineAvg [params] [set { ...}]")] [Description( "Tiles an item or npc by name around a targeted bounding box on the map's average Z elevation. Optional constructor parameters. Optional set property list." )] public static void OutlineAvg_OnCommand(CommandEventArgs e) { InternalAvg_OnCommand(e, true); } private enum TileZType { Start, Fixed, MapAverage } private class TileState { public readonly string[] m_Args; public readonly int m_FixedZ; public readonly bool m_Outline; public readonly TileZType m_ZType; public TileState(TileZType zType, int fixedZ, string[] args, bool outline) { m_ZType = zType; m_FixedZ = fixedZ; m_Args = args; m_Outline = outline; } } }