using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using Server.Items; using CPA = Server.CommandPropertyAttribute; namespace Server.Commands { public class Add { private static Type m_EntityType = typeof(IEntity); private static Type m_ConstructibleType = typeof(ConstructibleAttribute); private static Type m_EnumType = typeof(Enum); private static Type m_TypeType = typeof(Type); private static Type m_ParsableType = typeof(ParsableAttribute); private static Type[] m_ParseTypes = { typeof(string) }; private static object[] m_ParseArgs = new object[1]; private static Type[] m_SignedNumerics = { typeof(long), typeof(int), typeof(short), typeof(sbyte) }; private static Type[] m_UnsignedNumerics = { typeof(ulong), typeof(uint), typeof(ushort), typeof(byte) }; public static void Initialize() { 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) { StringBuilder sb = new StringBuilder(); sb.AppendFormat("{0} {1} building ", from.AccessLevel, CommandLogging.Format(from)); if (start == end) sb.AppendFormat("at {0} in {1}", start, from.Map); else sb.AppendFormat("from {0} to {1} in {2}", start, end, from.Map); sb.Append(":"); for (int i = 0; i < args.Length; ++i) sb.AppendFormat(" \"{0}\"", args[i]); CommandLogging.WriteLine(from, sb.ToString()); string name = args[0]; FixArgs(ref args); string[,] props = null; for (int i = 0; i < args.Length; ++i) if (Insensitive.Equals(args[i], "set")) { int remains = args.Length - i - 1; if (remains >= 2) { props = new string[remains / 2, 2]; remains /= 2; for (int j = 0; j < remains; ++j) { props[j, 0] = args[i + j * 2 + 1]; props[j, 1] = args[i + j * 2 + 2]; } FixSetString(ref args, i); } break; } Type type = ScriptCompiler.FindTypeByName(name); if (!IsEntity(type)) { from.SendMessage("No type with that name was found."); return; } DateTime time = DateTime.UtcNow; int built = BuildObjects(from, type, start, end, args, props, packs, outline, mapAvg); if (built > 0) from.SendMessage("{0} object{1} generated in {2:F1} seconds.", built, built != 1 ? "s" : "", (DateTime.UtcNow - time).TotalSeconds); else SendUsage(type, from); } public static void FixSetString(ref string[] args, int index) { string[] old = args; args = new string[index]; Array.Copy(old, 0, args, 0, index); } public static void FixArgs(ref string[] args) { string[] old = args; args = new string[args.Length - 1]; Array.Copy(old, 1, args, 0, args.Length); } public static int BuildObjects(Mobile from, Type type, Point3D start, Point3D end, string[] 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)]; PropertyInfo[] allProps = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public); for (int i = 0; i < realProps.Length; ++i) { PropertyInfo thisProp = null; string propName = props[i, 0]; for (int j = 0; thisProp == null && j < allProps.Length; ++j) if (Insensitive.Equals(propName, allProps[j].Name)) thisProp = allProps[j]; if (thisProp == null) { from.SendMessage("Property not found: {0}", propName); } else { CPA attr = Properties.GetCPA(thisProp); if (attr == null) from.SendMessage("Property ({0}) not found.", propName); else if (from.AccessLevel < attr.WriteLevel) from.SendMessage("Setting this property ({0}) requires at least {1} access level.", propName, Mobile.GetAccessLevelName(attr.WriteLevel)); else if (!thisProp.CanWrite || attr.ReadOnly) from.SendMessage("Property ({0}) is read only.", propName); else realProps[i] = thisProp; } } } ConstructorInfo[] ctors = type.GetConstructors(); for (int i = 0; i < ctors.Length; ++i) { ConstructorInfo ctor = ctors[i]; if (!IsConstructible(ctor, from.AccessLevel)) continue; int totalParams = 0; // Handle optional constructors ParameterInfo[] paramList = ctor.GetParameters(); for (int j = 0; j < paramList.Length; j++) if (!paramList[j].HasDefaultValue) totalParams += 1; if (args.Length >= totalParams && args.Length <= paramList.Length) { object[] paramValues = ParseValues(paramList, args); if (paramValues == null) continue; int 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, string[] args) { object[] values = new object[paramList.Length]; for (int i = 0, a = 0; i < paramList.Length; i++) { ParameterInfo param = paramList[i]; object value = ParseValue(param.ParameterType, a < args.Length ? args[a++] : null); if (value != null) values[i] = value; else if (param.HasDefaultValue) values[i] = Type.Missing; else return null; } return values; } public static object ParseValue(Type type, string value) { try { if (IsEnum(type)) return Enum.Parse(type, value, true); if (IsType(type)) return ScriptCompiler.FindTypeByName(value); if (IsParsable(type)) return ParseParsable(type, value); object obj = value; if (value?.StartsWith("0x") == true) { if (IsSignedNumeric(type)) obj = Convert.ToInt64(value.Substring(2), 16); else if (IsUnsignedNumeric(type)) obj = Convert.ToUInt64(value.Substring(2), 16); else obj = Convert.ToInt32(value.Substring(2), 16); } if (obj == null && !type.IsValueType) return null; return Convert.ChangeType(obj, type); } catch { return null; } } public static IEntity Build(Mobile from, ConstructorInfo ctor, object[] values, string[,] props, PropertyInfo[] realProps, ref bool sendError) { object built = ctor.Invoke(values); if (built != null && realProps != null) { bool hadError = false; for (int i = 0; i < realProps.Length; ++i) { if (realProps[i] == null) continue; string 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 { Map map = from.Map; int width = end.X - start.X + 1; int 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 {0} objects, please wait.", objectCount); bool sendError = true; StringBuilder sb = new StringBuilder(); sb.Append("Serials: "); if (packs != null) { for (int i = 0; i < packs.Count; ++i) { IEntity built = Build(from, ctor, values, props, realProps, ref sendError); sb.AppendFormat("0x{0:X}; ", built.Serial.Value); 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 { int z = start.Z; for (int x = start.X; x <= end.X; ++x) for (int 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); IEntity built = Build(from, ctor, values, props, realProps, ref sendError); sb.AppendFormat("0x{0:X}; ", built.Serial.Value); 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) { ConstructorInfo[] ctors = type.GetConstructors(); bool foundCtor = false; for (int i = 0; i < ctors.Length; ++i) { ConstructorInfo ctor = ctors[i]; if (!IsConstructible(ctor, from.AccessLevel)) continue; if (!foundCtor) { foundCtor = true; from.SendMessage("Usage:"); } SendCtor(type, ctor, from); } if (!foundCtor) from.SendMessage("That type is not marked constructible."); } public static void SendCtor(Type type, ConstructorInfo ctor, Mobile from) { ParameterInfo[] paramList = ctor.GetParameters(); StringBuilder sb = new StringBuilder(); sb.Append(type.Name); for (int 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, Map map, Point3D start, Point3D end, TileState ts) { bool 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) { Mobile from = e.Mobile; if (e.Length >= 1) BoundingBoxPicker.Begin(from, (map, start, end) => TileBox_Callback(from, map, start, end, new TileState(TileZType.Start, 0, e.Arguments, outline))); else from.SendMessage("Format: {0} [params] [set {{ ...}}]", outline ? "Outline" : "Tile"); } private static void InternalRXYZ_OnCommand(CommandEventArgs e, bool outline) { if (e.Length >= 6) { Point3D p = new Point3D(e.Mobile.X + e.GetInt32(0), e.Mobile.Y + e.GetInt32(1), e.Mobile.Z + e.GetInt32(4)); Point3D p2 = new Point3D(p.X + e.GetInt32(2) - 1, p.Y + e.GetInt32(3) - 1, p.Z); string[] subArgs = new string[e.Length - 5]; for (int i = 0; i < subArgs.Length; ++i) subArgs[i] = e.Arguments[i + 5]; Invoke(e.Mobile, p, p2, subArgs, null, outline); } else { e.Mobile.SendMessage( "Format: {0}RXYZ [params] [set {{ ...}}]", outline ? "Outline" : "Tile"); } } private static void InternalXYZ_OnCommand(CommandEventArgs e, bool outline) { if (e.Length >= 6) { Point3D p = new Point3D(e.GetInt32(0), e.GetInt32(1), e.GetInt32(4)); Point3D p2 = new Point3D(p.X + e.GetInt32(2) - 1, p.Y + e.GetInt32(3) - 1, e.GetInt32(4)); string[] subArgs = new string[e.Length - 5]; for (int i = 0; i < subArgs.Length; ++i) subArgs[i] = e.Arguments[i + 5]; Invoke(e.Mobile, p, p2, subArgs, null, outline); } else { e.Mobile.SendMessage( "Format: {0}XYZ [params] [set {{ ...}}]", outline ? "Outline" : "Tile"); } } private static void InternalZ_OnCommand(CommandEventArgs e, bool outline) { Mobile from = e.Mobile; if (e.Length >= 2) { string[] subArgs = new string[e.Length - 1]; for (int i = 0; i < subArgs.Length; ++i) subArgs[i] = e.Arguments[i + 1]; BoundingBoxPicker.Begin(from, (map, start, end) => TileBox_Callback(from, map, start, end, new TileState(TileZType.Fixed, e.GetInt32(0), subArgs, outline))); } else { from.SendMessage("Format: {0}Z [params] [set {{ ...}}]", outline ? "Outline" : "Tile"); } } private static void InternalAvg_OnCommand(CommandEventArgs e, bool outline) { Mobile from = e.Mobile; if (e.Length >= 1) BoundingBoxPicker.Begin(from, (map, start, end) => TileBox_Callback(from, map, start, end, new TileState(TileZType.MapAverage, 0, e.Arguments, outline))); else from.SendMessage("Format: {0}Avg [params] [set {{ ...}}]", outline ? "Outline" : "Tile"); } [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); } public static bool IsEntity(Type t) { return m_EntityType.IsAssignableFrom(t); } public static bool IsConstructible(ConstructorInfo ctor, AccessLevel accessLevel) { object[] attrs = ctor.GetCustomAttributes(m_ConstructibleType, false); return attrs.Length != 0 && accessLevel >= ((ConstructibleAttribute)attrs[0]).AccessLevel; } public static bool IsEnum(Type type) { return type.IsSubclassOf(m_EnumType); } public static bool IsType(Type type) { return type == m_TypeType || type.IsSubclassOf(m_TypeType); } public static bool IsParsable(Type type) { return type.IsDefined(m_ParsableType, false); } public static object ParseParsable(Type type, string value) { MethodInfo method = type.GetMethod("Parse", m_ParseTypes); m_ParseArgs[0] = value; return method?.Invoke(null, m_ParseArgs); } public static bool IsSignedNumeric(Type type) { for (int i = 0; i < m_SignedNumerics.Length; ++i) if (type == m_SignedNumerics[i]) return true; return false; } public static bool IsUnsignedNumeric(Type type) { for (int i = 0; i < m_UnsignedNumerics.Length; ++i) if (type == m_UnsignedNumerics[i]) return true; return false; } private enum TileZType { Start, Fixed, MapAverage } private class TileState { public string[] m_Args; public int m_FixedZ; public bool m_Outline; public 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; } } } }