Reorganizes Project (#41)

This commit is contained in:
Kamron Batman 2019-08-02 18:13:40 -07:00 committed by GitHub
parent 08bf44af9a
commit 3614a66aee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3499 changed files with 79 additions and 55 deletions

View file

@ -0,0 +1,696 @@
using System;
using System.Collections.Generic;
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<Container> 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<Container> 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<Container> 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} <type> [params] [set {{<propertyName> <value> ...}}]",
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 <x> <y> <w> <h> <z> <type> [params] [set {{<propertyName> <value> ...}}]",
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 <x> <y> <w> <h> <z> <type> [params] [set {{<propertyName> <value> ...}}]",
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 <z> <type> [params] [set {{<propertyName> <value> ...}}]",
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 <type> [params] [set {{<propertyName> <value> ...}}]",
outline ? "Outline" : "Tile");
}
[Usage("Tile <name> [params] [set {<propertyName> <value> ...}]")]
[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 <x> <y> <w> <h> <z> <name> [params] [set {<propertyName> <value> ...}]")]
[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 <x> <y> <w> <h> <z> <name> [params] [set {<propertyName> <value> ...}]")]
[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 <z> <name> [params] [set {<propertyName> <value> ...}]")]
[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 <name> [params] [set {<propertyName> <value> ...}]")]
[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 <name> [params] [set {<propertyName> <value> ...}]")]
[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 <x> <y> <w> <h> <z> <name> [params] [set {<propertyName> <value> ...}]")]
[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 <x> <y> <w> <h> <z> <name> [params] [set {<propertyName> <value> ...}]")]
[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 <z> <name> [params] [set {<propertyName> <value> ...}]")]
[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 <name> [params] [set {<propertyName> <value> ...}]")]
[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;
}
}
}
}

View file

@ -0,0 +1,34 @@
using System;
namespace Server
{
public class UsageAttribute : Attribute
{
public UsageAttribute(string usage)
{
Usage = usage;
}
public string Usage{ get; }
}
public class DescriptionAttribute : Attribute
{
public DescriptionAttribute(string description)
{
Description = description;
}
public string Description{ get; }
}
public class AliasesAttribute : Attribute
{
public AliasesAttribute(params string[] aliases)
{
Aliases = aliases;
}
public string[] Aliases{ get; }
}
}

View file

@ -0,0 +1,418 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Server.Commands.Generic;
using Server.Gumps;
using Server.Network;
namespace Server.Commands
{
public class Batch : BaseCommand
{
public Batch()
{
Commands = new[] { "Batch" };
ListOptimized = true;
}
public BaseCommandImplementor Scope{ get; set; }
public string Condition{ get; set; } = "";
public List<BatchCommand> BatchCommands{ get; } = new List<BatchCommand>();
public override void ExecuteList(CommandEventArgs e, List<object> list)
{
if (list.Count == 0)
{
LogFailure("Nothing was found to use this command on.");
return;
}
try
{
BaseCommand[] commands = new BaseCommand[BatchCommands.Count];
CommandEventArgs[] eventArgs = new CommandEventArgs[BatchCommands.Count];
for (int i = 0; i < BatchCommands.Count; ++i)
{
BatchCommand bc = BatchCommands[i];
bc.GetDetails(out string commandString, out string argString, out string[] args);
BaseCommand command = Scope.Commands[commandString];
commands[i] = command;
eventArgs[i] = new CommandEventArgs(e.Mobile, commandString, argString, args);
if (command == null)
{
e.Mobile.SendMessage(
"That is either an invalid command name or one that does not support this modifier: {0}.",
commandString);
return;
}
if (e.Mobile.AccessLevel < command.AccessLevel)
{
e.Mobile.SendMessage("You do not have access to that command: {0}.", commandString);
return;
}
if (!command.ValidateArgs(Scope, eventArgs[i])) return;
}
for (int i = 0; i < commands.Length; ++i)
{
BaseCommand command = commands[i];
BatchCommand bc = BatchCommands[i];
if (list.Count > 20)
CommandLogging.Enabled = false;
List<object> usedList;
if (Utility.InsensitiveCompare(bc.Object, "Current") == 0)
{
usedList = list;
}
else
{
Dictionary<Type, PropertyInfo[]> propertyChains = new Dictionary<Type, PropertyInfo[]>();
usedList = new List<object>(list.Count);
for (int j = 0; j < list.Count; ++j)
{
object obj = list[j];
if (obj == null)
continue;
Type type = obj.GetType();
string failReason = "";
if (!propertyChains.TryGetValue(type, out PropertyInfo[] chain))
propertyChains[type] = chain = Properties.GetPropertyInfoChain(e.Mobile, type, bc.Object,
PropertyAccess.Read, ref failReason);
if (chain == null)
continue;
PropertyInfo endProp = Properties.GetPropertyInfo(ref obj, chain, ref failReason);
if (endProp == null)
continue;
try
{
obj = endProp.GetValue(obj, null);
if (obj != null)
usedList.Add(obj);
}
catch
{
// ignored
}
}
}
command.ExecuteList(eventArgs[i], usedList);
if (list.Count > 20)
CommandLogging.Enabled = true;
command.Flush(e.Mobile, list.Count > 20);
}
}
catch (Exception ex)
{
e.Mobile.SendMessage(ex.Message);
}
}
public bool Run(Mobile from)
{
if (Scope == null)
{
from.SendMessage("You must select the batch command scope.");
return false;
}
if (Condition.Length > 0 && !Scope.SupportsConditionals)
{
from.SendMessage("This command scope does not support conditionals.");
return false;
}
if (Condition.Length > 0 && !Utility.InsensitiveStartsWith(Condition, "where"))
{
from.SendMessage("The condition field must start with \"where\".");
return false;
}
string[] args = CommandSystem.Split(Condition);
Scope.Process(from, this, args);
return true;
}
public static void Initialize()
{
CommandSystem.Register("Batch", AccessLevel.Counselor, Batch_OnCommand);
}
[Usage("Batch")]
[Description("Allows multiple commands to be run at the same time.")]
public static void Batch_OnCommand(CommandEventArgs e)
{
e.Mobile.SendGump(new BatchGump(e.Mobile, new Batch()));
}
}
public class BatchCommand
{
public BatchCommand(string command, string obj)
{
Command = command;
Object = obj;
}
public string Command{ get; set; }
public string Object{ get; set; }
public void GetDetails(out string command, out string argString, out string[] args)
{
int indexOf = Command.IndexOf(' ');
if (indexOf >= 0)
{
argString = Command.Substring(indexOf + 1);
command = Command.Substring(0, indexOf);
args = CommandSystem.Split(argString);
}
else
{
argString = "";
command = Command.ToLower();
args = new string[0];
}
}
}
public class BatchGump : BaseGridGump
{
private Batch m_Batch;
private Mobile m_From;
public BatchGump(Mobile from, Batch batch) : base(30, 30)
{
m_From = from;
m_Batch = batch;
Render();
}
public void Render()
{
AddNewPage();
/* Header */
AddEntryHeader(20);
AddEntryHtml(180, Center("Batch Commands"));
AddEntryHeader(20);
AddNewLine();
AddEntryHeader(9);
AddEntryLabel(191, "Run Batch");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, GetButtonID(1, 0, 0), ArrowRightWidth, ArrowRightHeight);
AddNewLine();
AddBlankLine();
/* Scope */
AddEntryHeader(20);
AddEntryHtml(180, Center("Scope"));
AddEntryHeader(20);
AddNewLine();
AddEntryHeader(9);
AddEntryLabel(191, m_Batch.Scope == null ? "Select Scope" : m_Batch.Scope.Accessors[0]);
AddEntryButton(20, ArrowRightID1, ArrowRightID2, GetButtonID(1, 0, 1), ArrowRightWidth, ArrowRightHeight);
AddNewLine();
AddBlankLine();
/* Condition */
AddEntryHeader(20);
AddEntryHtml(180, Center("Condition"));
AddEntryHeader(20);
AddNewLine();
AddEntryHeader(9);
AddEntryText(202, 0, m_Batch.Condition);
AddEntryHeader(9);
AddNewLine();
AddBlankLine();
/* Commands */
AddEntryHeader(20);
AddEntryHtml(180, Center("Commands"));
AddEntryHeader(20);
for (int i = 0; i < m_Batch.BatchCommands.Count; ++i)
{
BatchCommand bc = m_Batch.BatchCommands[i];
AddNewLine();
AddImageTiled(CurrentX, CurrentY, 9, 2, 0x24A8);
AddImageTiled(CurrentX, CurrentY + 2, 2, EntryHeight + OffsetSize + EntryHeight - 4, 0x24A8);
AddImageTiled(CurrentX, CurrentY + EntryHeight + OffsetSize + EntryHeight - 2, 9, 2, 0x24A8);
AddImageTiled(CurrentX + 3, CurrentY + 3, 6, EntryHeight + EntryHeight - 4 - OffsetSize, HeaderGumpID);
IncreaseX(9);
AddEntryText(202, 1 + i * 2, bc.Command);
AddEntryHeader(9, 2);
AddNewLine();
IncreaseX(9);
AddEntryText(202, 2 + i * 2, bc.Object);
}
AddNewLine();
AddEntryHeader(9);
AddEntryLabel(191, "Add New Command");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, GetButtonID(1, 0, 2), ArrowRightWidth, ArrowRightHeight);
FinishPage();
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (!SplitButtonID(info.ButtonID, 1, out int type, out int index))
return;
TextRelay entry = info.GetTextEntry(0);
if (entry != null)
m_Batch.Condition = entry.Text;
for (int i = m_Batch.BatchCommands.Count - 1; i >= 0; --i)
{
BatchCommand sc = m_Batch.BatchCommands[i];
entry = info.GetTextEntry(1 + i * 2);
if (entry != null)
sc.Command = entry.Text;
entry = info.GetTextEntry(2 + i * 2);
if (entry != null)
sc.Object = entry.Text;
if (sc.Command.Length == 0 && sc.Object.Length == 0)
m_Batch.BatchCommands.RemoveAt(i);
}
switch (type)
{
case 0: // main
{
switch (index)
{
case 0: // run
{
m_Batch.Run(m_From);
break;
}
case 1: // set scope
{
m_From.SendGump(new BatchScopeGump(m_From, m_Batch));
return;
}
case 2: // add command
{
m_Batch.BatchCommands.Add(new BatchCommand("", ""));
break;
}
}
break;
}
}
m_From.SendGump(new BatchGump(m_From, m_Batch));
}
}
public class BatchScopeGump : BaseGridGump
{
private Batch m_Batch;
private Mobile m_From;
public BatchScopeGump(Mobile from, Batch batch) : base(30, 30)
{
m_From = from;
m_Batch = batch;
Render();
}
public void Render()
{
AddNewPage();
/* Header */
AddEntryHeader(20);
AddEntryHtml(140, Center("Change Scope"));
AddEntryHeader(20);
/* Options */
for (int i = 0; i < BaseCommandImplementor.Implementors.Count; ++i)
{
BaseCommandImplementor impl = BaseCommandImplementor.Implementors[i];
if (m_From.AccessLevel < impl.AccessLevel)
continue;
AddNewLine();
AddEntryLabel(20 + OffsetSize + 140, impl.Accessors[0]);
AddEntryButton(20, ArrowRightID1, ArrowRightID2, GetButtonID(1, 0, i), ArrowRightWidth, ArrowRightHeight);
}
FinishPage();
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (SplitButtonID(info.ButtonID, 1, out int type, out int index))
switch (type)
{
case 0:
{
if (index < BaseCommandImplementor.Implementors.Count)
{
BaseCommandImplementor impl = BaseCommandImplementor.Implementors[index];
if (m_From.AccessLevel >= impl.AccessLevel)
m_Batch.Scope = impl;
}
break;
}
}
m_From.SendGump(new BatchGump(m_From, m_Batch));
}
}
}

View file

@ -0,0 +1,63 @@
using Server.Targeting;
namespace Server
{
public delegate void BoundingBoxCallback(Map map, Point3D start, Point3D end);
public static class BoundingBoxPicker
{
public static void Begin(Mobile from, BoundingBoxCallback callback)
{
from.SendMessage("Target the first location of the bounding box.");
from.Target = new PickTarget(callback);
}
private class PickTarget : Target
{
private BoundingBoxCallback m_Callback;
private bool m_First;
private Map m_Map;
private Point3D m_Store;
public PickTarget(BoundingBoxCallback callback) : this(Point3D.Zero, true, null, callback)
{
}
public PickTarget(Point3D store, bool first, Map map, BoundingBoxCallback callback) : base(-1, true, TargetFlags.None)
{
m_Store = store;
m_First = first;
m_Map = map;
m_Callback = callback;
}
protected override void OnTarget(Mobile from, object targeted)
{
if (!(targeted is IPoint3D p))
return;
if (p is Item item)
p = item.GetWorldTop();
if (m_First)
{
from.SendMessage("Target another location to complete the bounding box.");
from.Target = new PickTarget(new Point3D(p), false, from.Map, m_Callback);
}
else if (from.Map != m_Map)
{
from.SendMessage("Both locations must reside on the same map.");
}
else if (m_Map != null && m_Map != Map.Internal && m_Callback != null)
{
Point3D start = m_Store;
Point3D end = new Point3D(p);
Utility.FixPoints(ref start, ref end);
m_Callback(m_Map, start, end);
}
}
}
}
}

View file

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Server.Mobiles;
using Server.Network;
namespace Server.Commands
{
public class ConvertPlayers
{
public static void Initialize()
{
CommandSystem.Register("ConvertPlayers", AccessLevel.Administrator, Convert_OnCommand);
}
public static void Convert_OnCommand(CommandEventArgs e)
{
e.Mobile.SendMessage(
"Converting all players to PlayerMobile. You will be disconnected. Please Restart the server after the world has finished saving.");
List<Mobile> mobs = new List<Mobile>(World.Mobiles.Values);
int count = 0;
foreach (Mobile m in mobs)
if (m.Player && !(m is PlayerMobile))
{
count++;
m.NetState?.Dispose();
PlayerMobile pm = new PlayerMobile(m.Serial);
pm.DefaultMobileInit();
List<Item> copy = new List<Item>(m.Items);
for (int i = 0; i < copy.Count; i++)
pm.AddItem(copy[i]);
CopyProps(pm, m);
for (int i = 0; i < m.Skills.Length; i++)
{
pm.Skills[i].Base = m.Skills[i].Base;
pm.Skills[i].SetLockNoRelay(m.Skills[i].Lock);
}
World.Mobiles[m.Serial] = pm;
}
if (count > 0)
{
NetState.ProcessDisposedQueue();
World.Save();
Console.WriteLine("{0} players have been converted to PlayerMobile. {1}.", count,
Core.Service ? "The server is now restarting" : "Press any key to restart the server");
if (!Core.Service)
Console.ReadKey(true);
Core.Kill(true);
}
else
{
e.Mobile.SendMessage("Couldn't find any Players to convert.");
}
}
private static PropertyInfo[] _mobProps =
typeof(Mobile).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(prop => prop.CanRead && prop.CanWrite).ToArray();
private static void CopyProps(Mobile to, Mobile from)
{
foreach (PropertyInfo prop in _mobProps)
{
try
{
prop.SetValue(to, prop.GetValue(from, null), null);
}
catch
{
// ignored
}
}
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
namespace Server.Commands
{
public static class DragEffects
{
public static void Initialize()
{
CommandSystem.Register("DragEffects", AccessLevel.Developer, DragEffects_OnCommand);
}
[Usage("DragEffects [enable=false]")]
[Description("Enables or disables the item drag and drop effects.")]
public static void DragEffects_OnCommand(CommandEventArgs e)
{
if (e.Length == 0)
{
e.Mobile.SendMessage("Drag effects are currently {0}.", Mobile.DragEffects ? "enabled" : "disabled");
}
else
{
Mobile.DragEffects = e.GetBoolean(0);
e.Mobile.SendMessage("Drag effects have been {0}.", Mobile.DragEffects ? "enabled" : "disabled");
}
}
}
}

View file

@ -0,0 +1,135 @@
using System;
using System.Reflection;
using Server.Items;
using Server.Targeting;
namespace Server.Commands
{
public class Dupe
{
public static void Initialize()
{
CommandSystem.Register("Dupe", AccessLevel.GameMaster, Dupe_OnCommand);
CommandSystem.Register("DupeInBag", AccessLevel.GameMaster, DupeInBag_OnCommand);
}
[Usage("Dupe [amount]")]
[Description("Dupes a targeted item.")]
private static void Dupe_OnCommand(CommandEventArgs e)
{
int amount = 1;
if (e.Length >= 1)
amount = e.GetInt32(0);
e.Mobile.Target = new DupeTarget(false, amount > 0 ? amount : 1);
e.Mobile.SendMessage("What do you wish to dupe?");
}
[Usage("DupeInBag <count>")]
[Description("Dupes an item at it's current location (count) number of times.")]
private static void DupeInBag_OnCommand(CommandEventArgs e)
{
int amount = 1;
if (e.Length >= 1)
amount = e.GetInt32(0);
e.Mobile.Target = new DupeTarget(true, amount > 0 ? amount : 1);
e.Mobile.SendMessage("What do you wish to dupe?");
}
public static void CopyProperties(Item dest, Item src)
{
PropertyInfo[] props = src.GetType().GetProperties();
for (int i = 0; i < props.Length; i++)
try
{
if (props[i].CanRead && props[i].CanWrite) props[i].SetValue(dest, props[i].GetValue(src, null), null);
}
catch
{
//Console.WriteLine( "Denied" );
}
}
private class DupeTarget : Target
{
private int m_Amount;
private bool m_InBag;
public DupeTarget(bool inbag, int amount)
: base(15, false, TargetFlags.None)
{
m_InBag = inbag;
m_Amount = amount;
}
protected override void OnTarget(Mobile from, object targ)
{
bool done = false;
if (!(targ is Item))
{
from.SendMessage("You can only dupe items.");
return;
}
CommandLogging.WriteLine(from, "{0} {1} duping {2} (inBag={3}; amount={4})", from.AccessLevel,
CommandLogging.Format(from), CommandLogging.Format(targ), m_InBag, m_Amount);
Item copy = (Item)targ;
Container pack = null;
if (m_InBag)
{
if (copy.Parent is Container cont)
pack = cont;
else if (copy.Parent is Mobile m)
pack = m.Backpack;
}
else
{
pack = from.Backpack;
}
Type t = copy.GetType();
//ConstructorInfo[] info = t.GetConstructors();
ConstructorInfo c = t.GetConstructor(Type.EmptyTypes);
if (c != null)
try
{
from.SendMessage("Duping {0}...", m_Amount);
for (int i = 0; i < m_Amount; i++)
if (c.Invoke(null) is Item newItem)
{
CopyProperties(newItem, copy); //copy.Dupe( item, copy.Amount );
copy.OnAfterDuped(newItem);
newItem.Parent = null;
if (pack != null)
pack.DropItem(newItem);
else
newItem.MoveToWorld(from.Location, from.Map);
newItem.InvalidateProperties();
CommandLogging.WriteLine(from, "{0} {1} duped {2} creating {3}", from.AccessLevel,
CommandLogging.Format(from), CommandLogging.Format(targ),
CommandLogging.Format(newItem));
}
from.SendMessage("Done");
done = true;
}
catch
{
from.SendMessage("Error!");
return;
}
if (!done) from.SendMessage("Unable to dupe. Item must have a 0 parameter constructor.");
}
}
}
}

View file

@ -0,0 +1,79 @@
using System.Collections.Generic;
using System.IO;
using Server.Items;
namespace Server.Commands
{
public class ExportCommand
{
private const string ExportFile = @"C:\Uo\WorldForge\items.wsc";
public static void Initialize()
{
CommandSystem.Register("ExportWSC", AccessLevel.Administrator, Export_OnCommand);
}
public static void Export_OnCommand(CommandEventArgs e)
{
StreamWriter w = new StreamWriter(ExportFile);
List<Item> remove = new List<Item>();
int count = 0;
e.Mobile.SendMessage("Exporting all static items to \"{0}\"...", ExportFile);
e.Mobile.SendMessage("This will delete all static items in the world. Please make a backup.");
foreach (Item item in World.Items.Values)
if ((item is Static || item is BaseFloor || item is BaseWall)
&& item.RootParent == null)
{
w.WriteLine("SECTION WORLDITEM {0}", count);
w.WriteLine("{");
w.WriteLine("SERIAL {0}", item.Serial);
w.WriteLine("NAME #");
w.WriteLine("NAME2 #");
w.WriteLine("ID {0}", item.ItemID);
w.WriteLine("X {0}", item.X);
w.WriteLine("Y {0}", item.Y);
w.WriteLine("Z {0}", item.Z);
w.WriteLine("COLOR {0}", item.Hue);
w.WriteLine("CONT -1");
w.WriteLine("TYPE 0");
w.WriteLine("AMOUNT 1");
w.WriteLine("WEIGHT 255");
w.WriteLine("OWNER -1");
w.WriteLine("SPAWN -1");
w.WriteLine("VALUE 1");
w.WriteLine("}");
w.WriteLine("");
count++;
remove.Add(item);
w.Flush();
}
w.Close();
foreach (Item item in remove)
item.Delete();
e.Mobile.SendMessage("Export complete. Exported {0} statics.", count);
}
}
}
/*SECTION WORLDITEM 1
{
SERIAL 1073741830
NAME #
NAME2 #
ID 1709
X 1439
Y 1613
Z 20
CONT -1
TYPE 12
AMOUNT 1
WEIGHT 25500
OWNER -1
SPAWN -1
VALUE 1
}*/

View file

@ -0,0 +1,402 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using Server.Items;
namespace Server.Commands
{
public class Categorization
{
private static CategoryEntry m_RootItems, m_RootMobiles;
private static Type typeofItem = typeof(Item);
private static Type typeofMobile = typeof(Mobile);
private static Type typeofConstructible = typeof(ConstructibleAttribute);
public static CategoryEntry Items
{
get
{
if (m_RootItems == null)
Load();
return m_RootItems;
}
}
public static CategoryEntry Mobiles
{
get
{
if (m_RootMobiles == null)
Load();
return m_RootMobiles;
}
}
public static void Initialize()
{
CommandSystem.Register("RebuildCategorization", AccessLevel.Administrator, RebuildCategorization_OnCommand);
}
[Usage("RebuildCategorization")]
[Description("Rebuilds the categorization data file used by the Add command.")]
public static void RebuildCategorization_OnCommand(CommandEventArgs e)
{
CategoryEntry root = new CategoryEntry(null, "Add Menu", new[] { Items, Mobiles });
Export(root, "Data/objects.xml", "Objects");
e.Mobile.SendMessage("Categorization menu rebuilt.");
}
public static void Export(CategoryEntry ce, string fileName, string title)
{
XmlTextWriter xml = new XmlTextWriter(fileName, Encoding.UTF8);
xml.Indentation = 1;
xml.IndentChar = '\t';
xml.Formatting = Formatting.Indented;
xml.WriteStartDocument(true);
RecurseExport(xml, ce);
xml.Flush();
xml.Close();
}
public static void RecurseExport(XmlTextWriter xml, CategoryEntry ce)
{
xml.WriteStartElement("category");
xml.WriteAttributeString("title", ce.Title);
List<CategoryEntry> subCats = new List<CategoryEntry>(ce.SubCategories);
subCats.Sort(new CategorySorter());
for (int i = 0; i < subCats.Count; ++i)
RecurseExport(xml, subCats[i]);
ce.Matched.Sort(new CategoryTypeSorter());
for (int i = 0; i < ce.Matched.Count; ++i)
{
CategoryTypeEntry cte = ce.Matched[i];
xml.WriteStartElement("object");
xml.WriteAttributeString("type", cte.Type.ToString());
if (cte.Object is Item item)
{
int itemID = item.ItemID;
if (item is BaseAddon addon && addon.Components.Count == 1)
itemID = addon.Components[0].ItemID;
if (itemID > TileData.MaxItemValue)
itemID = 1;
xml.WriteAttributeString("gfx", XmlConvert.ToString(itemID));
int hue = item.Hue & 0x7FFF;
if ((hue & 0x4000) != 0)
hue = 0;
if (hue != 0)
xml.WriteAttributeString("hue", XmlConvert.ToString(hue));
item.Delete();
}
else if (cte.Object is Mobile mob)
{
int itemID = ShrinkTable.Lookup(mob, 1);
xml.WriteAttributeString("gfx", XmlConvert.ToString(itemID));
int hue = mob.Hue & 0x7FFF;
if ((hue & 0x4000) != 0)
hue = 0;
if (hue != 0)
xml.WriteAttributeString("hue", XmlConvert.ToString(hue));
mob.Delete();
}
xml.WriteEndElement();
}
xml.WriteEndElement();
}
public static void Load()
{
List<Type> types = new List<Type>();
AddTypes(Core.Assembly, types);
for (int i = 0; i < ScriptCompiler.Assemblies.Length; ++i)
AddTypes(ScriptCompiler.Assemblies[i], types);
m_RootItems = Load(types, "Data/items.cfg");
m_RootMobiles = Load(types, "Data/mobiles.cfg");
}
private static CategoryEntry Load(List<Type> types, string config)
{
CategoryLine[] lines = CategoryLine.Load(config);
if (lines.Length > 0)
{
int index = 0;
CategoryEntry root = new CategoryEntry(null, lines, ref index);
Fill(root, types);
return root;
}
return new CategoryEntry();
}
private static bool IsConstructible(Type type)
{
if (!type.IsSubclassOf(typeofItem) && !type.IsSubclassOf(typeofMobile))
return false;
ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes);
return ctor?.IsDefined(typeofConstructible, false) == true;
}
private static void AddTypes(Assembly asm, List<Type> types)
{
Type[] allTypes = asm.GetTypes();
for (int i = 0; i < allTypes.Length; ++i)
{
Type type = allTypes[i];
if (type.IsAbstract)
continue;
if (IsConstructible(type))
types.Add(type);
}
}
private static void Fill(CategoryEntry root, List<Type> list)
{
for (int i = 0; i < list.Count; ++i)
{
Type type = list[i];
CategoryEntry match = GetDeepestMatch(root, type);
if (match == null)
continue;
try
{
match.Matched.Add(new CategoryTypeEntry(type));
}
catch
{
// ignored
}
}
}
private static CategoryEntry GetDeepestMatch(CategoryEntry root, Type type)
{
if (!root.IsMatch(type))
return null;
for (int i = 0; i < root.SubCategories.Length; ++i)
{
CategoryEntry check = GetDeepestMatch(root.SubCategories[i], type);
if (check != null)
return check;
}
return root;
}
}
public class CategorySorter : IComparer<CategoryEntry>
{
public int Compare(CategoryEntry x, CategoryEntry y)
{
string a = x?.Title;
string b = y?.Title;
if (a == null && b == null)
return 0;
if (a == null)
return 1;
return a.CompareTo(b);
}
}
public class CategoryTypeSorter : IComparer<CategoryTypeEntry>
{
public int Compare(CategoryTypeEntry x, CategoryTypeEntry y)
{
string a = x?.Type.Name;
string b = y?.Type.Name;
if (a == null && b == null)
return 0;
if (a == null)
return 1;
return a.CompareTo(b);
}
}
public class CategoryTypeEntry
{
public CategoryTypeEntry(Type type)
{
Type = type;
Object = Activator.CreateInstance(type);
}
public Type Type{ get; }
public object Object{ get; }
}
public class CategoryEntry
{
public CategoryEntry(CategoryEntry parent = null, string title = "(empty)", CategoryEntry[] subCats = null)
{
Parent = parent;
Title = title;
SubCategories = subCats ?? new CategoryEntry[0];
Matches = new Type[0];
Matched = new List<CategoryTypeEntry>();
}
public CategoryEntry(CategoryEntry parent, CategoryLine[] lines, ref int index)
{
Parent = parent;
string text = lines[index].Text;
int start = text.IndexOf('(');
if (start < 0)
throw new FormatException($"Input string not correctly formatted ('{text}')");
Title = text.Substring(0, start).Trim();
int end = text.IndexOf(')', ++start);
if (end < start)
throw new FormatException($"Input string not correctly formatted ('{text}')");
text = text.Substring(start, end - start);
string[] split = text.Split(';');
List<Type> list = new List<Type>();
for (int i = 0; i < split.Length; ++i)
{
Type type = ScriptCompiler.FindTypeByName(split[i].Trim());
if (type == null)
Console.WriteLine("Match type not found ('{0}')", split[i].Trim());
else
list.Add(type);
}
Matches = list.ToArray();
list.Clear();
int ourIndentation = lines[index].Indentation;
++index;
List<CategoryEntry> entryList = new List<CategoryEntry>();
while (index < lines.Length && lines[index].Indentation > ourIndentation)
entryList.Add(new CategoryEntry(this, lines, ref index));
SubCategories = entryList.ToArray();
entryList.Clear();
Matched = new List<CategoryTypeEntry>();
}
public string Title{ get; }
public Type[] Matches{ get; }
public CategoryEntry Parent{ get; }
public CategoryEntry[] SubCategories{ get; }
public List<CategoryTypeEntry> Matched{ get; }
public bool IsMatch(Type type)
{
bool isMatch = false;
for (int i = 0; !isMatch && i < Matches.Length; ++i)
isMatch = type == Matches[i] || type.IsSubclassOf(Matches[i]);
return isMatch;
}
}
public class CategoryLine
{
public CategoryLine(string input)
{
int index;
for (index = 0; index < input.Length; ++index)
if (char.IsLetter(input, index))
break;
if (index >= input.Length)
throw new FormatException($"Input string not correctly formatted ('{input}')");
Indentation = index;
Text = input.Substring(index);
}
public int Indentation{ get; }
public string Text{ get; }
public static CategoryLine[] Load(string path)
{
List<CategoryLine> list = new List<CategoryLine>();
if (File.Exists(path))
using (StreamReader ip = new StreamReader(path))
{
string line;
while ((line = ip.ReadLine()) != null)
list.Add(new CategoryLine(line));
}
return list.ToArray();
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,137 @@
using System.Collections.Generic;
namespace Server.Commands.Generic
{
public enum ObjectTypes
{
Both,
Items,
Mobiles,
All
}
public abstract class BaseCommand
{
private List<MessageEntry> m_Responses = new List<MessageEntry>();
private List<MessageEntry> m_Failures = new List<MessageEntry>();
public bool ListOptimized{ get; set; }
public string[] Commands{ get; set; }
public string Usage{ get; set; }
public string Description{ get; set; }
public AccessLevel AccessLevel{ get; set; }
public ObjectTypes ObjectTypes{ get; set; }
public CommandSupport Supports{ get; set; }
public static bool IsAccessible(Mobile from, object obj)
{
if (from.AccessLevel >= AccessLevel.Administrator || obj == null)
return true;
Mobile mob = null;
if (obj is Mobile m)
mob = m;
else if (obj is Item item)
mob = item.RootParent as Mobile;
return mob == null || mob == from || from.AccessLevel > mob.AccessLevel;
}
public virtual void ExecuteList(CommandEventArgs e, List<object> list)
{
for (int i = 0; i < list.Count; ++i)
Execute(e, list[i]);
}
public virtual void Execute(CommandEventArgs e, object obj)
{
}
public virtual bool ValidateArgs(BaseCommandImplementor impl, CommandEventArgs e)
{
return true;
}
public void AddResponse(string message)
{
for (int i = 0; i < m_Responses.Count; ++i)
{
MessageEntry entry = m_Responses[i];
if (entry.m_Message == message)
{
++entry.m_Count;
return;
}
}
if (m_Responses.Count == 10)
return;
m_Responses.Add(new MessageEntry(message));
}
public void LogFailure(string message)
{
for (int i = 0; i < m_Failures.Count; ++i)
{
MessageEntry entry = m_Failures[i];
if (entry.m_Message == message)
{
++entry.m_Count;
return;
}
}
if (m_Failures.Count == 10)
return;
m_Failures.Add(new MessageEntry(message));
}
public void Flush(Mobile from, bool flushToLog)
{
if (m_Responses.Count > 0)
for (int i = 0; i < m_Responses.Count; ++i)
{
MessageEntry entry = m_Responses[i];
from.SendMessage(entry.ToString());
if (flushToLog)
CommandLogging.WriteLine(from, entry.ToString());
}
else
for (int i = 0; i < m_Failures.Count; ++i)
from.SendMessage(m_Failures[i].ToString());
m_Responses.Clear();
m_Failures.Clear();
}
private class MessageEntry
{
public int m_Count;
public string m_Message;
public MessageEntry(string message)
{
m_Message = message;
m_Count = 1;
}
public override string ToString()
{
return m_Count > 1 ? $"{m_Message} ({m_Count})" : m_Message;
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,203 @@
using System.Collections.Generic;
using Server.Gumps;
using Server.Items;
using Server.Multis;
using Server.Targeting;
namespace Server.Commands.Generic
{
public class DesignInsertCommand : BaseCommand
{
public enum DesignInsertResult
{
Valid,
InvalidItem,
NotInHouse,
OutsideHouseBounds
}
public DesignInsertCommand()
{
AccessLevel = AccessLevel.GameMaster;
Supports = CommandSupport.Single | CommandSupport.Area;
Commands = new[] { "DesignInsert" };
ObjectTypes = ObjectTypes.Items;
Usage = "DesignInsert [allItems=false]";
Description = "Inserts multiple targeted items into a customizable house's design.";
}
public static void Initialize()
{
TargetCommands.Register(new DesignInsertCommand());
}
public static DesignInsertResult ProcessInsert(Item item, bool staticsOnly, out HouseFoundation house)
{
house = null;
if (item == null || item is BaseMulti || item is HouseSign || staticsOnly && !(item is Static))
return DesignInsertResult.InvalidItem;
house = BaseHouse.FindHouseAt(item) as HouseFoundation;
if (house == null)
return DesignInsertResult.NotInHouse;
int x = item.X - house.X;
int y = item.Y - house.Y;
int z = item.Z - house.Z;
if (!TryInsertIntoState(house.CurrentState, item.ItemID, x, y, z))
return DesignInsertResult.OutsideHouseBounds;
TryInsertIntoState(house.DesignState, item.ItemID, x, y, z);
item.Delete();
return DesignInsertResult.Valid;
}
private static bool TryInsertIntoState(DesignState state, int itemID, int x, int y, int z)
{
MultiComponentList mcl = state.Components;
if (x < mcl.Min.X || y < mcl.Min.Y || x > mcl.Max.X || y > mcl.Max.Y)
return false;
mcl.Add(itemID, x, y, z);
state.OnRevised();
return true;
}
#region Single targeting mode
public override void Execute(CommandEventArgs e, object obj)
{
Target t = new DesignInsertTarget(new List<HouseFoundation>(), e.Length < 1 || !e.GetBoolean(0));
t.Invoke(e.Mobile, obj);
}
private class DesignInsertTarget : Target
{
private List<HouseFoundation> m_Foundations;
private bool m_StaticsOnly;
public DesignInsertTarget(List<HouseFoundation> foundations, bool staticsOnly)
: base(-1, false, TargetFlags.None)
{
m_Foundations = foundations;
m_StaticsOnly = staticsOnly;
}
protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType)
{
if (m_Foundations.Count != 0)
{
from.SendMessage("Your changes have been committed. Updating...");
foreach (HouseFoundation house in m_Foundations)
house.Delta(ItemDelta.Update);
}
}
protected override void OnTarget(Mobile from, object obj)
{
DesignInsertResult result = ProcessInsert(obj as Item, m_StaticsOnly, out HouseFoundation house);
switch (result)
{
case DesignInsertResult.Valid:
{
if (m_Foundations.Count == 0)
from.SendMessage(
"The item has been inserted into the house design. Press ESC when you are finished.");
else
from.SendMessage("The item has been inserted into the house design.");
if (!m_Foundations.Contains(house))
m_Foundations.Add(house);
break;
}
case DesignInsertResult.InvalidItem:
{
from.SendMessage("That cannot be inserted. Try again.");
break;
}
case DesignInsertResult.NotInHouse:
case DesignInsertResult.OutsideHouseBounds:
{
from.SendMessage("That item is not inside a customizable house. Try again.");
break;
}
}
from.Target = new DesignInsertTarget(m_Foundations, m_StaticsOnly);
}
}
#endregion
#region Area targeting mode
public override void ExecuteList(CommandEventArgs e, List<object> list)
{
Mobile from = e.Mobile;
from.SendGump(new WarningGump(1060637, 30720,
$"You are about to insert {list.Count} objects. This cannot be undone without a full server revert.<br><br>Continue?",
0xFFC000, 420, 280, okay => OnConfirmCallback(from, okay, list, e.Length < 1 || !e.GetBoolean(0))));
AddResponse("Awaiting confirmation...");
}
private void OnConfirmCallback(Mobile from, bool okay, List<object> list, bool staticsOnly)
{
bool flushToLog = false;
if (okay)
{
List<HouseFoundation> foundations = new List<HouseFoundation>();
flushToLog = list.Count > 20;
for (int i = 0; i < list.Count; ++i)
{
DesignInsertResult result = ProcessInsert(list[i] as Item, staticsOnly, out HouseFoundation house);
switch (result)
{
case DesignInsertResult.Valid:
{
AddResponse("The item has been inserted into the house design.");
if (!foundations.Contains(house))
foundations.Add(house);
break;
}
case DesignInsertResult.InvalidItem:
{
LogFailure("That cannot be inserted.");
break;
}
case DesignInsertResult.NotInHouse:
case DesignInsertResult.OutsideHouseBounds:
{
LogFailure("That item is not inside a customizable house.");
break;
}
}
}
foreach (HouseFoundation house in foundations)
house.Delta(ItemDelta.Update);
}
else
{
AddResponse("Command aborted.");
}
Flush(from, flushToLog);
}
#endregion
}
}

View file

@ -0,0 +1,568 @@
using System.Collections.Generic;
using System.Reflection;
using Server.Gumps;
using Server.Network;
using Server.Targets;
namespace Server.Commands.Generic
{
public class InterfaceCommand : BaseCommand
{
public InterfaceCommand()
{
AccessLevel = AccessLevel.GameMaster;
Supports = CommandSupport.Complex | CommandSupport.Simple;
Commands = new[] { "Interface" };
ObjectTypes = ObjectTypes.Both;
Usage = "Interface [view <properties ...>]";
Description = "Opens an interface to interact with matched objects. Generally used with condition arguments.";
ListOptimized = true;
}
public override void ExecuteList(CommandEventArgs e, List<object> list)
{
if (list.Count > 0)
{
List<string> columns = new List<string> { "Object" };
if (e.Length > 0)
{
int offset = 0;
if (Insensitive.Equals(e.GetString(0), "view"))
++offset;
while (offset < e.Length)
columns.Add(e.GetString(offset++));
}
e.Mobile.SendGump(new InterfaceGump(e.Mobile, columns.ToArray(), list, 0, null));
}
else
{
AddResponse("No matching objects found.");
}
}
}
public class InterfaceGump : BaseGridGump
{
private const int EntriesPerPage = 15;
private string[] m_Columns;
private Mobile m_From;
private List<object> m_List;
private int m_Page;
private object m_Select;
public InterfaceGump(Mobile from, string[] columns, List<object> list, int page, object select) : base(30, 30)
{
m_From = from;
m_Columns = columns;
m_List = list;
m_Page = page;
m_Select = select;
Render();
}
public void Render()
{
AddNewPage();
if (m_Page > 0)
AddEntryButton(20, ArrowLeftID1, ArrowLeftID2, 1, ArrowLeftWidth, ArrowLeftHeight);
else
AddEntryHeader(20);
AddEntryHtml(40 + m_Columns.Length * 130 - 20 + (m_Columns.Length - 2) * OffsetSize, Center(
$"Page {m_Page + 1} of {(m_List.Count + EntriesPerPage - 1) / EntriesPerPage}"));
if ((m_Page + 1) * EntriesPerPage < m_List.Count)
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 2, ArrowRightWidth, ArrowRightHeight);
else
AddEntryHeader(20);
if (m_Columns.Length > 1)
{
AddNewLine();
for (int i = 0; i < m_Columns.Length; ++i)
{
if (i > 0 && m_List.Count > 0)
{
object obj = m_List[0];
if (obj != null)
{
string failReason = null;
PropertyInfo[] chain = Properties.GetPropertyInfoChain(m_From, obj.GetType(), m_Columns[i],
PropertyAccess.Read, ref failReason);
if (chain?.Length > 0)
{
m_Columns[i] = "";
for (int j = 0; j < chain.Length; ++j)
{
if (j > 0)
m_Columns[i] += '.';
m_Columns[i] += chain[j].Name;
}
}
}
}
AddEntryHtml(130 + (i == 0 ? 40 : 0), m_Columns[i]);
}
AddEntryHeader(20);
}
for (int i = m_Page * EntriesPerPage, line = 0; line < EntriesPerPage && i < m_List.Count; ++i, ++line)
{
AddNewLine();
object obj = m_List[i];
bool isDeleted = false;
if (obj is Item item)
{
if (!(isDeleted = item.Deleted))
AddEntryHtml(40 + 130, item.GetType().Name);
}
else if (obj is Mobile mob)
{
if (!(isDeleted = mob.Deleted))
AddEntryHtml(40 + 130, mob.Name);
}
if (isDeleted)
{
AddEntryHtml(40 + 130, "(deleted)");
for (int j = 1; j < m_Columns.Length; ++j)
AddEntryHtml(130, "---");
AddEntryHeader(20);
}
else
{
for (int j = 1; j < m_Columns.Length; ++j)
{
object src = obj;
string value;
string failReason = "";
PropertyInfo[] chain = Properties.GetPropertyInfoChain(m_From, src.GetType(), m_Columns[j],
PropertyAccess.Read, ref failReason);
if (chain == null || chain.Length == 0)
{
value = "---";
}
else
{
PropertyInfo p = Properties.GetPropertyInfo(ref src, chain, ref failReason);
if (p == null)
value = "---";
else
value = PropertiesGump.ValueToString(src, p);
}
AddEntryHtml(130, value);
}
bool isSelected = m_Select != null && obj == m_Select;
AddEntryButton(20, isSelected ? 9762 : ArrowRightID1, isSelected ? 9763 : ArrowRightID2, 3 + i,
ArrowRightWidth, ArrowRightHeight);
}
}
FinishPage();
}
public override void OnResponse(NetState sender, RelayInfo info)
{
switch (info.ButtonID)
{
case 1:
{
if (m_Page > 0)
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page - 1, m_Select));
break;
}
case 2:
{
if ((m_Page + 1) * EntriesPerPage < m_List.Count)
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page + 1, m_Select));
break;
}
default:
{
int v = info.ButtonID - 3;
if (v >= 0 && v < m_List.Count)
{
object obj = m_List[v];
if (!BaseCommand.IsAccessible(m_From, obj))
{
m_From.SendLocalizedMessage(500447); // That is not accessible.
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Select));
break;
}
if (obj is Item item && !item.Deleted)
m_From.SendGump(new InterfaceItemGump(m_From, m_Columns, m_List, m_Page, item));
else if (obj is Mobile mobile && !mobile.Deleted)
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, mobile));
else
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Select));
}
break;
}
}
}
}
public class InterfaceItemGump : BaseGridGump
{
private string[] m_Columns;
private Mobile m_From;
private Item m_Item;
private List<object> m_List;
private int m_Page;
public InterfaceItemGump(Mobile from, string[] columns, List<object> list, int page, Item item) : base(30, 30)
{
m_From = from;
m_Columns = columns;
m_List = list;
m_Page = page;
m_Item = item;
Render();
}
public void Render()
{
AddNewPage();
AddEntryButton(20, ArrowLeftID1, ArrowLeftID2, 1, ArrowLeftWidth, ArrowLeftHeight);
AddEntryHtml(160, m_Item.GetType().Name);
AddEntryHeader(20);
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, "Properties");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 2, ArrowRightWidth, ArrowRightHeight);
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, "Delete");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 3, ArrowRightWidth, ArrowRightHeight);
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, "Go there");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 4, ArrowRightWidth, ArrowRightHeight);
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, "Move to target");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 5, ArrowRightWidth, ArrowRightHeight);
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, "Bring to pack");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 6, ArrowRightWidth, ArrowRightHeight);
FinishPage();
}
private void InvokeCommand(string ip)
{
CommandSystem.Handle(m_From, $"{CommandSystem.Prefix}{ip}");
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (m_Item.Deleted)
{
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Item));
return;
}
if (!BaseCommand.IsAccessible(m_From, m_Item))
{
m_From.SendMessage("That is no longer accessible.");
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Item));
return;
}
switch (info.ButtonID)
{
case 0:
case 1:
{
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Item));
break;
}
case 2: // Properties
{
m_From.SendGump(new InterfaceItemGump(m_From, m_Columns, m_List, m_Page, m_Item));
m_From.SendGump(new PropertiesGump(m_From, m_Item));
break;
}
case 3: // Delete
{
CommandLogging.WriteLine(m_From, "{0} {1} deleting {2}", m_From.AccessLevel,
CommandLogging.Format(m_From), CommandLogging.Format(m_Item));
m_Item.Delete();
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Item));
break;
}
case 4: // Go there
{
m_From.SendGump(new InterfaceItemGump(m_From, m_Columns, m_List, m_Page, m_Item));
InvokeCommand($"Go {m_Item.Serial.Value}");
break;
}
case 5: // Move to target
{
m_From.SendGump(new InterfaceItemGump(m_From, m_Columns, m_List, m_Page, m_Item));
m_From.Target = new MoveTarget(m_Item);
break;
}
case 6: // Bring to pack
{
Mobile owner = m_Item.RootParent as Mobile;
if (owner?.Map != null && owner.Map != Map.Internal &&
!BaseCommand.IsAccessible(m_From, owner) /* !m_From.CanSee( owner )*/)
{
m_From.SendMessage("You can not get what you can not see.");
}
else if (owner != null && (owner.Map == null || owner.Map == Map.Internal) && owner.Hidden &&
owner.AccessLevel >= m_From.AccessLevel)
{
m_From.SendMessage("You can not get what you can not see.");
}
else
{
m_From.SendGump(new InterfaceItemGump(m_From, m_Columns, m_List, m_Page, m_Item));
m_From.AddToBackpack(m_Item);
}
break;
}
}
}
}
public class InterfaceMobileGump : BaseGridGump
{
private string[] m_Columns;
private Mobile m_From;
private List<object> m_List;
private Mobile m_Mobile;
private int m_Page;
public InterfaceMobileGump(Mobile from, string[] columns, List<object> list, int page, Mobile mob)
: base(30, 30)
{
m_From = from;
m_Columns = columns;
m_List = list;
m_Page = page;
m_Mobile = mob;
Render();
}
public void Render()
{
AddNewPage();
AddEntryButton(20, ArrowLeftID1, ArrowLeftID2, 1, ArrowLeftWidth, ArrowLeftHeight);
AddEntryHtml(160, m_Mobile.Name);
AddEntryHeader(20);
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, "Properties");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 2, ArrowRightWidth, ArrowRightHeight);
if (!m_Mobile.Player)
{
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, "Delete");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 3, ArrowRightWidth, ArrowRightHeight);
}
if (m_Mobile != m_From)
{
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, "Go to there");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 4, ArrowRightWidth, ArrowRightHeight);
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, "Bring them here");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 5, ArrowRightWidth, ArrowRightHeight);
}
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, "Move to target");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 6, ArrowRightWidth, ArrowRightHeight);
if (m_From == m_Mobile || m_From.AccessLevel > m_Mobile.AccessLevel)
{
AddNewLine();
if (m_Mobile.Alive)
{
AddEntryHtml(20 + OffsetSize + 160, "Kill");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 7, ArrowRightWidth, ArrowRightHeight);
}
else
{
AddEntryHtml(20 + OffsetSize + 160, "Resurrect");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 8, ArrowRightWidth, ArrowRightHeight);
}
}
if (m_Mobile.NetState != null)
{
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, "Client");
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 9, ArrowRightWidth, ArrowRightHeight);
}
FinishPage();
}
private void InvokeCommand(string ip)
{
CommandSystem.Handle(m_From, $"{CommandSystem.Prefix}{ip}");
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (m_Mobile.Deleted)
{
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
return;
}
if (!BaseCommand.IsAccessible(m_From, m_Mobile))
{
m_From.SendMessage("That is no longer accessible.");
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
return;
}
switch (info.ButtonID)
{
case 0:
case 1:
{
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
break;
}
case 2: // Properties
{
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
m_From.SendGump(new PropertiesGump(m_From, m_Mobile));
break;
}
case 3: // Delete
{
if (!m_Mobile.Player)
{
CommandLogging.WriteLine(m_From, "{0} {1} deleting {2}", m_From.AccessLevel,
CommandLogging.Format(m_From), CommandLogging.Format(m_Mobile));
m_Mobile.Delete();
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
}
break;
}
case 4: // Go there
{
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
InvokeCommand($"Go {m_Mobile.Serial.Value}");
break;
}
case 5: // Bring them here
{
if (m_From.Map == null || m_From.Map == Map.Internal)
{
m_From.SendMessage("You cannot bring that person here.");
}
else
{
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
m_Mobile.MoveToWorld(m_From.Location, m_From.Map);
}
break;
}
case 6: // Move to target
{
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
m_From.Target = new MoveTarget(m_Mobile);
break;
}
case 7: // Kill
{
if (m_From == m_Mobile || m_From.AccessLevel > m_Mobile.AccessLevel)
m_Mobile.Kill();
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
break;
}
case 8: // Res
{
if (m_From == m_Mobile || m_From.AccessLevel > m_Mobile.AccessLevel)
{
m_Mobile.PlaySound(0x214);
m_Mobile.FixedEffect(0x376A, 10, 16);
m_Mobile.Resurrect();
}
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
break;
}
case 9: // Client
{
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
if (m_Mobile.NetState != null)
m_From.SendGump(new ClientGump(m_From, m_Mobile.NetState));
break;
}
}
}
}
}

View file

@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
namespace Server.Commands.Generic
{
public delegate BaseExtension ExtensionConstructor();
public sealed class ExtensionInfo
{
public ExtensionInfo(int order, string name, int size, ExtensionConstructor constructor)
{
Name = name;
Size = size;
Order = order;
Constructor = constructor;
}
public static Dictionary<string, ExtensionInfo> Table{ get; } =
new Dictionary<string, ExtensionInfo>(StringComparer.InvariantCultureIgnoreCase);
public int Order{ get; }
public string Name{ get; }
public int Size{ get; }
public bool IsFixedSize => Size >= 0;
public ExtensionConstructor Constructor{ get; }
public static void Register(ExtensionInfo ext)
{
Table[ext.Name] = ext;
}
}
public sealed class Extensions : List<BaseExtension>
{
public bool IsValid(object obj)
{
for (int i = 0; i < Count; ++i)
if (!this[i].IsValid(obj))
return false;
return true;
}
public void Filter(List<object> list)
{
for (int i = 0; i < Count; ++i)
this[i].Filter(list);
}
public static Extensions Parse(Mobile from, ref string[] args)
{
Extensions parsed = new Extensions();
int size = args.Length;
Type baseType = null;
for (int i = args.Length - 1; i >= 0; --i)
{
if (!ExtensionInfo.Table.TryGetValue(args[i], out ExtensionInfo extInfo))
continue;
if (extInfo.IsFixedSize && i != size - extInfo.Size - 1)
throw new Exception("Invalid extended argument count.");
BaseExtension ext = extInfo.Constructor();
ext.Parse(from, args, i + 1, size - i - 1);
if (ext is WhereExtension extension)
baseType = extension.Conditional.Type;
parsed.Add(ext);
size = i;
}
parsed.Sort((a, b) => a.Order - b.Order);
AssemblyEmitter emitter = null;
foreach (BaseExtension update in parsed)
update.Optimize(from, baseType, ref emitter);
if (size != args.Length)
{
string[] old = args;
args = new string[size];
for (int i = 0; i < args.Length; ++i)
args[i] = old[i];
}
return parsed;
}
}
public abstract class BaseExtension
{
public abstract ExtensionInfo Info{ get; }
public string Name => Info.Name;
public int Size => Info.Size;
public bool IsFixedSize => Info.IsFixedSize;
public int Order => Info.Order;
public virtual void Optimize(Mobile from, Type baseType, ref AssemblyEmitter assembly)
{
}
public virtual void Parse(Mobile from, string[] arguments, int offset, int size)
{
}
public virtual bool IsValid(object obj)
{
return true;
}
public virtual void Filter(List<object> list)
{
}
}
}

View file

@ -0,0 +1,549 @@
using System;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
namespace Server.Commands.Generic
{
public interface IConditional
{
bool Verify(object obj);
}
public interface ICondition
{
// Invoked during the constructor
void Construct(TypeBuilder typeBuilder, ILGenerator il, int index);
// Target object will be loaded on the stack
void Compile(MethodEmitter emitter);
}
public sealed class TypeCondition : ICondition
{
public static TypeCondition Default = new TypeCondition();
void ICondition.Construct(TypeBuilder typeBuilder, ILGenerator il, int index)
{
}
void ICondition.Compile(MethodEmitter emitter)
{
// The object was safely cast to be the conditionals type
// If it's null, then the type cast didn't work...
emitter.LoadNull();
emitter.Compare(OpCodes.Ceq);
emitter.LogicalNot();
}
}
public sealed class PropertyValue
{
public PropertyValue(Type type, object value)
{
Type = type;
Value = value;
}
public Type Type{ get; }
public object Value{ get; private set; }
public FieldInfo Field{ get; private set; }
public bool HasField => Field != null;
public void Load(MethodEmitter method)
{
if (Field != null)
{
method.LoadArgument(0);
method.LoadField(Field);
}
else if (Value == null)
{
method.LoadNull(Type);
}
else
{
if (Value is int i)
method.Load(i);
else if (Value is long l)
method.Load(l);
else if (Value is float f)
method.Load(f);
else if (Value is double d)
method.Load(d);
else if (Value is char c)
method.Load(c);
else if (Value is bool b)
method.Load(b);
else if (Value is string s)
method.Load(s);
else if (Value is Enum e)
method.Load(e);
else
throw new InvalidOperationException("Unrecognized comparison value.");
}
}
public void Acquire(TypeBuilder typeBuilder, ILGenerator il, string fieldName)
{
if (!(Value is string toParse))
return;
if (!Type.IsValueType && toParse == "null")
{
Value = null;
}
else if (Type == typeof(string))
{
if (toParse == @"@""null""")
toParse = "null";
Value = toParse;
}
else if (Type.IsEnum)
{
Value = Enum.Parse(Type, toParse, true);
}
else
{
MethodInfo parseMethod;
object[] parseArgs;
MethodInfo parseNumber = Type.GetMethod(
"Parse",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(string), typeof(NumberStyles) },
null
);
if (parseNumber != null)
{
NumberStyles style = NumberStyles.Integer;
if (Insensitive.StartsWith(toParse, "0x"))
{
style = NumberStyles.HexNumber;
toParse = toParse.Substring(2);
}
parseMethod = parseNumber;
parseArgs = new object[] { toParse, style };
}
else
{
MethodInfo parseGeneral = Type.GetMethod(
"Parse",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(string) },
null
);
parseMethod = parseGeneral;
parseArgs = new object[] { toParse };
}
if (parseMethod != null)
{
Value = parseMethod.Invoke(null, parseArgs);
if (!Type.IsPrimitive)
{
Field = typeBuilder.DefineField(
fieldName,
Type,
FieldAttributes.Private | FieldAttributes.InitOnly
);
// parseMethod.Invoke(null,
// parseArgs.Length == 2 ? new object[] {toParse, (int) parseArgs[1]} : new object[] {toParse});
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, toParse);
if (parseArgs.Length == 2) // dirty evil hack :-(
il.Emit(OpCodes.Ldc_I4, (int)parseArgs[1]);
il.Emit(OpCodes.Call, parseMethod);
il.Emit(OpCodes.Stfld, Field);
}
}
else
{
throw new InvalidOperationException(
$"Unable to convert string \"{Value}\" into type '{Type}'."
);
}
}
}
}
public abstract class PropertyCondition : ICondition
{
protected bool m_Not;
protected Property m_Property;
public PropertyCondition(Property property, bool not)
{
m_Property = property;
m_Not = not;
}
public abstract void Construct(TypeBuilder typeBuilder, ILGenerator il, int index);
public abstract void Compile(MethodEmitter emitter);
}
public enum StringOperator
{
Equal,
NotEqual,
Contains,
StartsWith,
EndsWith
}
public sealed class StringCondition : PropertyCondition
{
private bool m_IgnoreCase;
private StringOperator m_Operator;
private PropertyValue m_Value;
public StringCondition(Property property, bool not, StringOperator op, object value, bool ignoreCase)
: base(property, not)
{
m_Operator = op;
m_Value = new PropertyValue(property.Type, value);
m_IgnoreCase = ignoreCase;
}
public override void Construct(TypeBuilder typeBuilder, ILGenerator il, int index)
{
m_Value.Acquire(typeBuilder, il, "v" + index);
}
public override void Compile(MethodEmitter emitter)
{
bool inverse = false;
string methodName;
switch (m_Operator)
{
case StringOperator.Equal:
methodName = "Equals";
break;
case StringOperator.NotEqual:
methodName = "Equals";
inverse = true;
break;
case StringOperator.Contains:
methodName = "Contains";
break;
case StringOperator.StartsWith:
methodName = "StartsWith";
break;
case StringOperator.EndsWith:
methodName = "EndsWith";
break;
default:
throw new InvalidOperationException("Invalid string comparison operator.");
}
if (m_IgnoreCase || methodName == "Equals")
{
Type type = m_IgnoreCase ? typeof(Insensitive) : typeof(string);
emitter.BeginCall(
type.GetMethod(
methodName,
BindingFlags.Public | BindingFlags.Static,
null,
new[]
{
typeof(string),
typeof(string)
},
null
)
);
emitter.Chain(m_Property);
m_Value.Load(emitter);
emitter.FinishCall();
}
else
{
Label notNull = emitter.CreateLabel();
Label moveOn = emitter.CreateLabel();
LocalBuilder temp = emitter.AcquireTemp(m_Property.Type);
emitter.Chain(m_Property);
emitter.StoreLocal(temp);
emitter.LoadLocal(temp);
emitter.BranchIfTrue(notNull);
emitter.Load(false);
emitter.Pop();
emitter.Branch(moveOn);
emitter.MarkLabel(notNull);
emitter.LoadLocal(temp);
emitter.BeginCall(
typeof(string).GetMethod(
methodName,
BindingFlags.Public | BindingFlags.Instance,
null,
new[]
{
typeof(string)
},
null
)
);
m_Value.Load(emitter);
emitter.FinishCall();
emitter.MarkLabel(moveOn);
}
if (m_Not != inverse)
emitter.LogicalNot();
}
}
public enum ComparisonOperator
{
Equal,
NotEqual,
Greater,
GreaterEqual,
Lesser,
LesserEqual
}
public sealed class ComparisonCondition : PropertyCondition
{
private ComparisonOperator m_Operator;
private PropertyValue m_Value;
public ComparisonCondition(Property property, bool not, ComparisonOperator op, object value)
: base(property, not)
{
m_Operator = op;
m_Value = new PropertyValue(property.Type, value);
}
public override void Construct(TypeBuilder typeBuilder, ILGenerator il, int index)
{
m_Value.Acquire(typeBuilder, il, "v" + index);
}
public override void Compile(MethodEmitter emitter)
{
emitter.Chain(m_Property);
bool inverse = false;
bool couldCompare =
emitter.CompareTo(1, delegate { m_Value.Load(emitter); });
if (couldCompare)
{
emitter.Load(0);
switch (m_Operator)
{
case ComparisonOperator.Equal:
emitter.Compare(OpCodes.Ceq);
break;
case ComparisonOperator.NotEqual:
emitter.Compare(OpCodes.Ceq);
inverse = true;
break;
case ComparisonOperator.Greater:
emitter.Compare(OpCodes.Cgt);
break;
case ComparisonOperator.GreaterEqual:
emitter.Compare(OpCodes.Clt);
inverse = true;
break;
case ComparisonOperator.Lesser:
emitter.Compare(OpCodes.Clt);
break;
case ComparisonOperator.LesserEqual:
emitter.Compare(OpCodes.Cgt);
inverse = true;
break;
default:
throw new InvalidOperationException("Invalid comparison operator.");
}
}
else
{
// This type is -not- comparable
// We can only support == and != operations
m_Value.Load(emitter);
switch (m_Operator)
{
case ComparisonOperator.Equal:
emitter.Compare(OpCodes.Ceq);
break;
case ComparisonOperator.NotEqual:
emitter.Compare(OpCodes.Ceq);
inverse = true;
break;
case ComparisonOperator.Greater:
case ComparisonOperator.GreaterEqual:
case ComparisonOperator.Lesser:
case ComparisonOperator.LesserEqual:
throw new InvalidOperationException("Property does not support relational comparisons.");
default:
throw new InvalidOperationException("Invalid operator.");
}
}
if (m_Not != inverse)
emitter.LogicalNot();
}
}
public static class ConditionalCompiler
{
public static IConditional Compile(AssemblyEmitter assembly, Type objectType, ICondition[] conditions, int index)
{
TypeBuilder typeBuilder = assembly.DefineType(
"__conditional" + index,
TypeAttributes.Public,
typeof(object)
);
#region Constructor
{
ConstructorBuilder ctor = typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
Type.EmptyTypes
);
ILGenerator il = ctor.GetILGenerator();
// : base()
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
for (int i = 0; i < conditions.Length; ++i)
conditions[i].Construct(typeBuilder, il, i);
// return;
il.Emit(OpCodes.Ret);
}
#endregion
#region IComparer
typeBuilder.AddInterfaceImplementation(typeof(IConditional));
MethodBuilder compareMethod;
#region Compare
{
MethodEmitter emitter = new MethodEmitter(typeBuilder);
emitter.Define(
/* name */ "Verify",
/* attr */ MethodAttributes.Public | MethodAttributes.Virtual,
/* return */ typeof(bool),
/* params */ new[] { typeof(object) });
LocalBuilder obj = emitter.CreateLocal(objectType);
LocalBuilder eq = emitter.CreateLocal(typeof(bool));
emitter.LoadArgument(1);
emitter.CastAs(objectType);
emitter.StoreLocal(obj);
Label done = emitter.CreateLabel();
for (int i = 0; i < conditions.Length; ++i)
{
if (i > 0)
{
emitter.LoadLocal(eq);
emitter.BranchIfFalse(done);
}
emitter.LoadLocal(obj);
conditions[i].Compile(emitter);
emitter.StoreLocal(eq);
}
emitter.MarkLabel(done);
emitter.LoadLocal(eq);
emitter.Return();
typeBuilder.DefineMethodOverride(
emitter.Method,
typeof(IConditional).GetMethod(
"Verify",
new[]
{
typeof(object)
}
)
);
compareMethod = emitter.Method;
}
#endregion
#endregion
Type conditionalType = typeBuilder.CreateType();
return (IConditional)Activator.CreateInstance(conditionalType);
}
}
}

View file

@ -0,0 +1,259 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
namespace Server.Commands.Generic
{
public static class DistinctCompiler
{
public static IComparer<T> Compile<T>(AssemblyEmitter assembly, Type objectType, Property[] props)
{
TypeBuilder typeBuilder = assembly.DefineType(
"__distinct",
TypeAttributes.Public,
typeof(object)
);
#region Constructor
{
ConstructorBuilder ctor = typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
Type.EmptyTypes
);
ILGenerator il = ctor.GetILGenerator();
// : base()
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, typeof(T).GetConstructor(Type.EmptyTypes) ??
throw new Exception($"Could not find empty constructor for type {typeof(T).FullName}"));
// return;
il.Emit(OpCodes.Ret);
}
#endregion
#region IComparer
typeBuilder.AddInterfaceImplementation(typeof(IComparer<T>));
MethodBuilder compareMethod;
#region Compare
{
MethodEmitter emitter = new MethodEmitter(typeBuilder);
emitter.Define(
/* name */ "Compare",
/* attr */ MethodAttributes.Public | MethodAttributes.Virtual,
/* return */ typeof(int),
/* params */ new[] { typeof(T), typeof(T) });
LocalBuilder a = emitter.CreateLocal(objectType);
LocalBuilder b = emitter.CreateLocal(objectType);
LocalBuilder v = emitter.CreateLocal(typeof(int));
emitter.LoadArgument(1);
emitter.CastAs(objectType);
emitter.StoreLocal(a);
emitter.LoadArgument(2);
emitter.CastAs(objectType);
emitter.StoreLocal(b);
emitter.Load(0);
emitter.StoreLocal(v);
Label end = emitter.CreateLabel();
for (int i = 0; i < props.Length; ++i)
{
if (i > 0)
{
emitter.LoadLocal(v);
emitter.BranchIfTrue(end); // if ( v != 0 ) return v;
}
Property prop = props[i];
emitter.LoadLocal(a);
emitter.Chain(prop);
bool couldCompare =
emitter.CompareTo(1, delegate
{
emitter.LoadLocal(b);
emitter.Chain(prop);
});
if (!couldCompare)
throw new InvalidOperationException("Property is not comparable.");
emitter.StoreLocal(v);
}
emitter.MarkLabel(end);
emitter.LoadLocal(v);
emitter.Return();
typeBuilder.DefineMethodOverride(
emitter.Method,
typeof(IComparer<T>).GetMethod(
"Compare",
new[]
{
typeof(T),
typeof(T)
}
) ?? throw new Exception($"No Compare method found for type {typeof(T).FullName}")
);
compareMethod = emitter.Method;
}
#endregion
#endregion
#region IEqualityComparer
typeBuilder.AddInterfaceImplementation(typeof(IEqualityComparer<T>));
#region Equals
{
MethodEmitter emitter = new MethodEmitter(typeBuilder);
emitter.Define(
/* name */ "Equals",
/* attr */ MethodAttributes.Public | MethodAttributes.Virtual,
/* return */ typeof(bool),
/* params */ new[] { typeof(T), typeof(T) });
emitter.Generator.Emit(OpCodes.Ldarg_0);
emitter.Generator.Emit(OpCodes.Ldarg_1);
emitter.Generator.Emit(OpCodes.Ldarg_2);
emitter.Generator.Emit(OpCodes.Call, compareMethod);
emitter.Generator.Emit(OpCodes.Ldc_I4_0);
emitter.Generator.Emit(OpCodes.Ceq);
emitter.Generator.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(
emitter.Method,
typeof(IEqualityComparer<T>).GetMethod(
"Equals",
new[]
{
typeof(T),
typeof(T)
}
) ?? throw new Exception($"No Equals method found for type {typeof(T).FullName}")
);
}
#endregion
#region GetHashCode
{
MethodEmitter emitter = new MethodEmitter(typeBuilder);
emitter.Define(
/* name */ "GetHashCode",
/* attr */ MethodAttributes.Public | MethodAttributes.Virtual,
/* return */ typeof(int),
/* params */ new[] { typeof(T) });
LocalBuilder obj = emitter.CreateLocal(objectType);
emitter.LoadArgument(1);
emitter.CastAs(objectType);
emitter.StoreLocal(obj);
for (int i = 0; i < props.Length; ++i)
{
Property prop = props[i];
emitter.LoadLocal(obj);
emitter.Chain(prop);
Type active = emitter.Active;
MethodInfo getHashCode = active.GetMethod("GetHashCode", Type.EmptyTypes);
if (getHashCode == null)
getHashCode = typeof(T).GetMethod("GetHashCode", Type.EmptyTypes);
if (active != typeof(int))
{
if (!active.IsValueType)
{
LocalBuilder value = emitter.AcquireTemp(active);
Label valueNotNull = emitter.CreateLabel();
Label done = emitter.CreateLabel();
emitter.StoreLocal(value);
emitter.LoadLocal(value);
emitter.BranchIfTrue(valueNotNull);
emitter.Load(0);
emitter.Pop(typeof(int));
emitter.Branch(done);
emitter.MarkLabel(valueNotNull);
emitter.LoadLocal(value);
emitter.Call(getHashCode);
emitter.ReleaseTemp(value);
emitter.MarkLabel(done);
}
else
{
emitter.Call(getHashCode);
}
}
if (i > 0)
emitter.Xor();
}
emitter.Return();
typeBuilder.DefineMethodOverride(
emitter.Method,
typeof(IEqualityComparer<T>).GetMethod(
"GetHashCode",
new[]
{
typeof(T)
}
) ?? throw new Exception($"No GetHashCode method found for type {typeof(T).FullName}")
);
}
#endregion
#endregion
Type comparerType = typeBuilder.CreateType();
return (IComparer<T>)Activator.CreateInstance(comparerType);
}
}
}

View file

@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
namespace Server.Commands.Generic
{
public sealed class OrderInfo
{
private int m_Order;
public OrderInfo(Property property, bool isAscending)
{
Property = property;
IsAscending = isAscending;
}
public Property Property{ get; set; }
public bool IsAscending
{
get => m_Order > 0;
set => m_Order = value ? +1 : -1;
}
public bool IsDescending
{
get => m_Order < 0;
set => m_Order = value ? -1 : +1;
}
public int Sign
{
get => Math.Sign(m_Order);
set
{
m_Order = Math.Sign(value);
if (m_Order == 0)
throw new InvalidOperationException("Sign cannot be zero.");
}
}
}
public static class SortCompiler
{
public static IComparer<T> Compile<T>(AssemblyEmitter assembly, Type objectType, OrderInfo[] orders)
{
TypeBuilder typeBuilder = assembly.DefineType(
"__sort",
TypeAttributes.Public,
typeof(T)
);
#region Constructor
{
ConstructorBuilder ctor = typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
Type.EmptyTypes
);
ILGenerator il = ctor.GetILGenerator();
// : base()
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, typeof(T).GetConstructor(Type.EmptyTypes) ??
throw new Exception($"Could not find empty constructor for type {typeof(T).FullName}"));
// return;
il.Emit(OpCodes.Ret);
}
#endregion
#region IComparer
typeBuilder.AddInterfaceImplementation(typeof(IComparer<T>));
#region Compare
{
MethodEmitter emitter = new MethodEmitter(typeBuilder);
emitter.Define(
/* name */ "Compare",
/* attr */ MethodAttributes.Public | MethodAttributes.Virtual,
/* return */ typeof(int),
/* params */ new[] { typeof(T), typeof(T) });
LocalBuilder a = emitter.CreateLocal(objectType);
LocalBuilder b = emitter.CreateLocal(objectType);
LocalBuilder v = emitter.CreateLocal(typeof(int));
emitter.LoadArgument(1);
emitter.CastAs(objectType);
emitter.StoreLocal(a);
emitter.LoadArgument(2);
emitter.CastAs(objectType);
emitter.StoreLocal(b);
emitter.Load(0);
emitter.StoreLocal(v);
Label end = emitter.CreateLabel();
for (int i = 0; i < orders.Length; ++i)
{
if (i > 0)
{
emitter.LoadLocal(v);
emitter.BranchIfTrue(end); // if ( v != 0 ) return v;
}
OrderInfo orderInfo = orders[i];
Property prop = orderInfo.Property;
int sign = orderInfo.Sign;
emitter.LoadLocal(a);
emitter.Chain(prop);
bool couldCompare =
emitter.CompareTo(sign, delegate
{
emitter.LoadLocal(b);
emitter.Chain(prop);
});
if (!couldCompare)
throw new InvalidOperationException("Property is not comparable.");
emitter.StoreLocal(v);
}
emitter.MarkLabel(end);
emitter.LoadLocal(v);
emitter.Return();
typeBuilder.DefineMethodOverride(
emitter.Method,
typeof(IComparer<T>).GetMethod(
"Compare",
new[]
{
typeof(T),
typeof(T)
}
) ?? throw new Exception($"No Compare method found for type {typeof(T).FullName}")
);
}
#endregion
#endregion
Type comparerType = typeBuilder.CreateType();
return (IComparer<T>)Activator.CreateInstance(comparerType);
}
}
}

View file

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
namespace Server.Commands.Generic
{
public sealed class DistinctExtension : BaseExtension
{
public static ExtensionInfo ExtInfo =
new ExtensionInfo(30, "Distinct", -1, () => new DistinctExtension());
private IComparer<object> m_Comparer;
private List<Property> m_Properties;
public DistinctExtension()
{
m_Properties = new List<Property>();
}
public override ExtensionInfo Info => ExtInfo;
public static void Initialize()
{
ExtensionInfo.Register(ExtInfo);
}
public override void Optimize(Mobile from, Type baseType, ref AssemblyEmitter assembly)
{
if (baseType == null)
throw new Exception("Distinct extension may only be used in combination with an object conditional.");
foreach (Property prop in m_Properties)
{
prop.BindTo(baseType, PropertyAccess.Read);
prop.CheckAccess(from);
}
if (assembly == null)
assembly = new AssemblyEmitter("__dynamic");
m_Comparer = DistinctCompiler.Compile<object>(assembly, baseType, m_Properties.ToArray());
}
public override void Parse(Mobile from, string[] arguments, int offset, int size)
{
if (size < 1)
throw new Exception("Invalid distinction syntax.");
int end = offset + size;
while (offset < end)
{
string binding = arguments[offset++];
m_Properties.Add(new Property(binding));
}
}
public override void Filter(List<object> list)
{
if (m_Comparer == null)
throw new InvalidOperationException("The extension must first be optimized.");
List<object> copy = new List<object>(list);
copy.Sort(m_Comparer);
list.Clear();
object last = null;
for (int i = 0; i < copy.Count; ++i)
{
object obj = copy[i];
if (last == null || m_Comparer.Compare(obj, last) != 0)
{
list.Add(obj);
last = obj;
}
}
}
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
namespace Server.Commands.Generic
{
public sealed class LimitExtension : BaseExtension
{
public static ExtensionInfo ExtInfo = new ExtensionInfo(80, "Limit", 1, () => new LimitExtension());
public override ExtensionInfo Info => ExtInfo;
public int Limit{ get; private set; }
public static void Initialize()
{
ExtensionInfo.Register(ExtInfo);
}
public override void Parse(Mobile from, string[] arguments, int offset, int size)
{
Limit = Utility.ToInt32(arguments[offset]);
if (Limit < 0)
throw new Exception("Limit cannot be less than zero.");
}
public override void Filter(List<object> list)
{
if (list.Count > Limit)
list.RemoveRange(Limit, list.Count - Limit);
}
}
}

View file

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
namespace Server.Commands.Generic
{
public sealed class SortExtension : BaseExtension
{
public static ExtensionInfo ExtInfo = new ExtensionInfo(40, "Order", -1, () => new SortExtension());
private IComparer<object> m_Comparer;
private List<OrderInfo> m_Orders;
public SortExtension()
{
m_Orders = new List<OrderInfo>();
}
public override ExtensionInfo Info => ExtInfo;
public static void Initialize()
{
ExtensionInfo.Register(ExtInfo);
}
public override void Optimize(Mobile from, Type baseType, ref AssemblyEmitter assembly)
{
if (baseType == null)
throw new Exception("The ordering extension may only be used in combination with an object conditional.");
foreach (OrderInfo order in m_Orders)
{
order.Property.BindTo(baseType, PropertyAccess.Read);
order.Property.CheckAccess(from);
}
if (assembly == null)
assembly = new AssemblyEmitter("__dynamic");
m_Comparer = SortCompiler.Compile<object>(assembly, baseType, m_Orders.ToArray());
}
public override void Parse(Mobile from, string[] arguments, int offset, int size)
{
if (size < 1)
throw new Exception("Invalid ordering syntax.");
if (Insensitive.Equals(arguments[offset], "by"))
{
++offset;
--size;
if (size < 1)
throw new Exception("Invalid ordering syntax.");
}
int end = offset + size;
while (offset < end)
{
string binding = arguments[offset++];
bool isAscending = true;
if (offset < end)
{
string next = arguments[offset];
switch (next.ToLower())
{
case "+":
case "up":
case "asc":
case "ascending":
isAscending = true;
++offset;
break;
case "-":
case "down":
case "desc":
case "descending":
isAscending = false;
++offset;
break;
}
}
Property property = new Property(binding);
m_Orders.Add(new OrderInfo(property, isAscending));
}
}
public override void Filter(List<object> list)
{
if (m_Comparer == null)
throw new InvalidOperationException("The extension must first be optimized.");
list.Sort(m_Comparer);
}
}
}

View file

@ -0,0 +1,39 @@
using System;
namespace Server.Commands.Generic
{
public sealed class WhereExtension : BaseExtension
{
public static ExtensionInfo ExtInfo = new ExtensionInfo(20, "Where", -1, () => new WhereExtension());
public override ExtensionInfo Info => ExtInfo;
public ObjectConditional Conditional{ get; private set; }
public static void Initialize()
{
ExtensionInfo.Register(ExtInfo);
}
public override void Optimize(Mobile from, Type baseType, ref AssemblyEmitter assembly)
{
if (baseType == null)
throw new InvalidOperationException("Insanity.");
Conditional.Compile(ref assembly);
}
public override void Parse(Mobile from, string[] arguments, int offset, int size)
{
if (size < 1)
throw new Exception("Invalid condition syntax.");
Conditional = ObjectConditional.ParseDirect(from, arguments, offset, size);
}
public override bool IsValid(object obj)
{
return Conditional.CheckCondition(obj);
}
}
}

View file

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Server.Commands.Generic
{
public class AreaCommandImplementor : BaseCommandImplementor
{
public AreaCommandImplementor()
{
Accessors = new[] { "Area", "Group" };
SupportRequirement = CommandSupport.Area;
SupportsConditionals = true;
AccessLevel = AccessLevel.GameMaster;
Usage = "Area <command> [condition]";
Description =
"Invokes the command on all appropriate objects in a targeted area. Optional condition arguments can further restrict the set of objects.";
Instance = this;
}
public static AreaCommandImplementor Instance{ get; private set; }
public override void Process(Mobile from, BaseCommand command, string[] args)
{
BoundingBoxPicker.Begin(from, (map, start, end) => OnTarget(from, map, start, end, command, args));
}
public void OnTarget(Mobile from, Map map, Point3D start, Point3D end, BaseCommand command, string[] args)
{
try
{
Rectangle2D rect = new Rectangle2D(start.X, start.Y, end.X - start.X + 1, end.Y - start.Y + 1);
Extensions ext = Extensions.Parse(from, ref args);
if (!CheckObjectTypes(from, command, ext, out bool items, out bool mobiles))
return;
if (!(items || mobiles))
return;
IPooledEnumerable<IEntity> eable = map.GetObjectsInBounds(rect, items, mobiles);
List<object> objs = eable.Where(obj => !mobiles || !(obj is Mobile) || BaseCommand.IsAccessible(from, obj))
.Where(obj => ext.IsValid(obj)).Cast<object>().ToList();
eable.Free();
ext.Filter(objs);
RunCommand(from, objs, command, args);
}
catch (Exception ex)
{
from.SendMessage(ex.Message);
}
}
}
}

View file

@ -0,0 +1,295 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Commands.Generic
{
[Flags]
public enum CommandSupport
{
Single = 0x0001,
Global = 0x0002,
Online = 0x0004,
Multi = 0x0008,
Area = 0x0010,
Self = 0x0020,
Region = 0x0040,
Contained = 0x0080,
IPAddress = 0x0100,
All = Single | Global | Online | Multi | Area | Self | Region | Contained | IPAddress,
AllMobiles = All & ~Contained,
AllNPCs = All & ~(IPAddress | Online | Self | Contained),
AllItems = All & ~(IPAddress | Online | Self | Region),
Simple = Single | Multi,
Complex = Global | Online | Area | Region | Contained | IPAddress
}
public abstract class BaseCommandImplementor
{
private static List<BaseCommandImplementor> m_Implementors;
public BaseCommandImplementor()
{
Commands = new Dictionary<string, BaseCommand>(StringComparer.OrdinalIgnoreCase);
}
public bool SupportsConditionals{ get; set; }
public string[] Accessors{ get; set; }
public string Usage{ get; set; }
public string Description{ get; set; }
public AccessLevel AccessLevel{ get; set; }
public CommandSupport SupportRequirement{ get; set; }
public Dictionary<string, BaseCommand> Commands{ get; }
public static List<BaseCommandImplementor> Implementors
{
get
{
if (m_Implementors == null)
{
m_Implementors = new List<BaseCommandImplementor>();
RegisterImplementors();
}
return m_Implementors;
}
}
public static void RegisterImplementors()
{
Register(new RegionCommandImplementor());
Register(new GlobalCommandImplementor());
Register(new OnlineCommandImplementor());
Register(new SingleCommandImplementor());
Register(new SerialCommandImplementor());
Register(new MultiCommandImplementor());
Register(new AreaCommandImplementor());
Register(new SelfCommandImplementor());
Register(new ContainedCommandImplementor());
Register(new IPAddressCommandImplementor());
Register(new RangeCommandImplementor());
Register(new ScreenCommandImplementor());
Register(new FacetCommandImplementor());
}
public virtual void Compile(Mobile from, BaseCommand command, ref string[] args, ref object obj)
{
obj = null;
}
public virtual void Register(BaseCommand command)
{
for (int i = 0; i < command.Commands.Length; ++i)
Commands[command.Commands[i]] = command;
}
public bool CheckObjectTypes(Mobile from, BaseCommand command, Extensions ext, out bool items, out bool mobiles)
{
items = mobiles = false;
ObjectConditional cond = ObjectConditional.Empty;
foreach (BaseExtension check in ext)
if (check is WhereExtension extension)
{
cond = extension.Conditional;
break;
}
bool condIsItem = cond.IsItem;
bool condIsMobile = cond.IsMobile;
switch (command.ObjectTypes)
{
case ObjectTypes.All:
case ObjectTypes.Both:
{
if (condIsItem)
items = true;
if (condIsMobile)
mobiles = true;
break;
}
case ObjectTypes.Items:
{
if (condIsItem)
{
items = true;
}
else if (condIsMobile)
{
from.SendMessage("You may not use a mobile type condition for this command.");
return false;
}
break;
}
case ObjectTypes.Mobiles:
{
if (condIsMobile)
{
mobiles = true;
}
else if (condIsItem)
{
from.SendMessage("You may not use an item type condition for this command.");
return false;
}
break;
}
}
return true;
}
public void RunCommand(Mobile from, BaseCommand command, string[] args)
{
try
{
object obj = null;
Compile(from, command, ref args, ref obj);
RunCommand(from, obj, command, args);
}
catch (Exception ex)
{
from.SendMessage(ex.Message);
}
}
public string GenerateArgString(string[] args)
{
if (args.Length == 0)
return "";
// NOTE: this does not preserve the case where quotation marks are used on a single word
StringBuilder sb = new StringBuilder();
for (int i = 0; i < args.Length; ++i)
{
if (i > 0)
sb.Append(' ');
if (args[i].IndexOf(' ') >= 0)
{
sb.Append('"');
sb.Append(args[i]);
sb.Append('"');
}
else
{
sb.Append(args[i]);
}
}
return sb.ToString();
}
public void RunCommand(Mobile from, object obj, BaseCommand command, string[] args)
{
// try
// {
CommandEventArgs e = new CommandEventArgs(from, command.Commands[0], GenerateArgString(args), args);
if (!command.ValidateArgs(this, e))
return;
bool flushToLog = false;
if (obj is List<object> list)
{
if (list.Count > 20)
CommandLogging.Enabled = false;
else if (list.Count == 0)
command.LogFailure("Nothing was found to use this command on.");
command.ExecuteList(e, list);
if (list.Count > 20)
{
flushToLog = true;
CommandLogging.Enabled = true;
}
}
else if (obj != null)
{
if (command.ListOptimized)
command.ExecuteList(e, new List<object>{ obj });
else
command.Execute(e, obj);
}
command.Flush(from, flushToLog);
// }
// catch ( Exception ex )
// {
// from.SendMessage( ex.Message );
// }
}
public virtual void Process(Mobile from, BaseCommand command, string[] args)
{
RunCommand(from, command, args);
}
public virtual void Execute(CommandEventArgs e)
{
if (e.Length >= 1)
{
if (!Commands.TryGetValue(e.GetString(0), out BaseCommand command))
{
e.Mobile.SendMessage(
"That is either an invalid command name or one that does not support this modifier.");
}
else if (e.Mobile.AccessLevel < command.AccessLevel)
{
e.Mobile.SendMessage("You do not have access to that command.");
}
else
{
string[] oldArgs = e.Arguments;
string[] args = new string[oldArgs.Length - 1];
for (int i = 0; i < args.Length; ++i)
args[i] = oldArgs[i + 1];
Process(e.Mobile, command, args);
}
}
else
{
e.Mobile.SendMessage("You must supply a command name.");
}
}
public void Register()
{
if (Accessors == null)
return;
for (int i = 0; i < Accessors.Length; ++i)
CommandSystem.Register(Accessors[i], AccessLevel, Execute);
}
public static void Register(BaseCommandImplementor impl)
{
m_Implementors.Add(impl);
impl.Register();
}
}
}

View file

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using Server.Items;
using Server.Targeting;
namespace Server.Commands.Generic
{
public class ContainedCommandImplementor : BaseCommandImplementor
{
public ContainedCommandImplementor()
{
Accessors = new[] { "Contained" };
SupportRequirement = CommandSupport.Contained;
AccessLevel = AccessLevel.GameMaster;
Usage = "Contained <command> [condition]";
Description =
"Invokes the command on all child items in a targeted container. Optional condition arguments can further restrict the set of objects.";
}
public override void Process(Mobile from, BaseCommand command, string[] args)
{
if (command.ValidateArgs(this, new CommandEventArgs(from, command.Commands[0], GenerateArgString(args), args)))
from.BeginTarget(-1, command.ObjectTypes == ObjectTypes.All, TargetFlags.None,
(m, targeted) => OnTarget(m, targeted, command, args));
}
public void OnTarget(Mobile from, object targeted, BaseCommand command, string[] args)
{
if (!BaseCommand.IsAccessible(from, targeted))
{
from.SendLocalizedMessage(500447); // That is not accessible.
return;
}
if (command.ObjectTypes == ObjectTypes.Mobiles)
return; // sanity check
if (!(targeted is Container cont))
{
from.SendMessage("That is not a container.");
return;
}
try
{
Extensions ext = Extensions.Parse(from, ref args);
if (!CheckObjectTypes(from, command, ext, out bool items, out bool _))
return;
if (!items)
{
from.SendMessage("This command only works on items.");
return;
}
List<object> list = new List<object>();
foreach (Item item in cont.FindItemsByType<Item>())
{
if (ext.IsValid(item))
list.Add(item);
}
ext.Filter(list);
RunCommand(from, list, command, args);
}
catch (Exception e)
{
from.SendMessage(e.Message);
}
}
}
}

View file

@ -0,0 +1,31 @@
namespace Server.Commands.Generic
{
public class FacetCommandImplementor : BaseCommandImplementor
{
public FacetCommandImplementor()
{
Accessors = new[] { "Facet" };
SupportRequirement = CommandSupport.Area;
SupportsConditionals = true;
AccessLevel = AccessLevel.GameMaster;
Usage = "Facet <command> [condition]";
Description =
"Invokes the command on all appropriate objects within your facet's map bounds. Optional condition arguments can further restrict the set of objects.";
}
public override void Process(Mobile from, BaseCommand command, string[] args)
{
AreaCommandImplementor impl = AreaCommandImplementor.Instance;
if (impl == null)
return;
Map map = from.Map;
if (map == null || map == Map.Internal)
return;
impl.OnTarget(from, map, Point3D.Zero, new Point3D(map.Width - 1, map.Height - 1, 0), command, args);
}
}
}

View file

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
namespace Server.Commands.Generic
{
public class GlobalCommandImplementor : BaseCommandImplementor
{
public GlobalCommandImplementor()
{
Accessors = new[] { "Global" };
SupportRequirement = CommandSupport.Global;
SupportsConditionals = true;
AccessLevel = AccessLevel.Administrator;
Usage = "Global <command> [condition]";
Description =
"Invokes the command on all appropriate objects in the world. Optional condition arguments can further restrict the set of objects.";
}
public override void Compile(Mobile from, BaseCommand command, ref string[] args, ref object obj)
{
try
{
Extensions ext = Extensions.Parse(from, ref args);
if (!CheckObjectTypes(from, command, ext, out bool items, out bool mobiles))
return;
List<object> list = new List<object>();
if (items)
foreach (Item item in World.Items.Values)
if (ext.IsValid(item))
list.Add(item);
if (mobiles)
foreach (Mobile mob in World.Mobiles.Values)
if (ext.IsValid(mob))
list.Add(mob);
ext.Filter(list);
obj = list;
}
catch (Exception ex)
{
from.SendMessage(ex.Message);
}
}
}
}

View file

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Net;
using Server.Network;
namespace Server.Commands.Generic
{
public class IPAddressCommandImplementor : BaseCommandImplementor
{
public IPAddressCommandImplementor()
{
Accessors = new[] { "IPAddress" };
SupportRequirement = CommandSupport.IPAddress;
SupportsConditionals = true;
AccessLevel = AccessLevel.Administrator;
Usage = "IPAddress <command> [condition]";
Description =
"Invokes the command on one mobile from each IP address that is logged in. Optional condition arguments can further restrict the set of objects.";
}
public override void Compile(Mobile from, BaseCommand command, ref string[] args, ref object obj)
{
try
{
Extensions ext = Extensions.Parse(from, ref args);
if (!CheckObjectTypes(from, command, ext, out bool _, out bool mobiles))
return;
if (!mobiles) // sanity check
{
command.LogFailure("This command does not support items.");
return;
}
List<object> list = new List<object>();
List<IPAddress> addresses = new List<IPAddress>();
List<NetState> states = NetState.Instances;
for (int i = 0; i < states.Count; ++i)
{
NetState ns = states[i];
Mobile mob = ns.Mobile;
if (mob != null && !addresses.Contains(ns.Address) && ext.IsValid(mob))
{
list.Add(mob);
addresses.Add(ns.Address);
}
}
ext.Filter(list);
obj = list;
}
catch (Exception ex)
{
from.SendMessage(ex.Message);
}
}
}
}

View file

@ -0,0 +1,73 @@
using Server.Targeting;
namespace Server.Commands.Generic
{
public class MultiCommandImplementor : BaseCommandImplementor
{
public MultiCommandImplementor()
{
Accessors = new[] { "Multi", "m" };
SupportRequirement = CommandSupport.Multi;
AccessLevel = AccessLevel.Counselor;
Usage = "Multi <command>";
Description = "Invokes the command on multiple targeted objects.";
}
public override void Process(Mobile from, BaseCommand command, string[] args)
{
if (command.ValidateArgs(this, new CommandEventArgs(from, command.Commands[0], GenerateArgString(args), args)))
from.BeginTarget(-1, command.ObjectTypes == ObjectTypes.All, TargetFlags.None,
(m, targeted) => OnTarget(m, targeted, command, args));
}
public void OnTarget(Mobile from, object targeted, BaseCommand command, string[] args)
{
if (!BaseCommand.IsAccessible(from, targeted))
{
from.SendLocalizedMessage(500447); // That is not accessible.
from.BeginTarget(-1, command.ObjectTypes == ObjectTypes.All, TargetFlags.None,
(m, t) => OnTarget(m, t, command, args));
return;
}
switch (command.ObjectTypes)
{
case ObjectTypes.Both:
{
if (!(targeted is Item || targeted is Mobile))
{
from.SendMessage("This command does not work on that.");
return;
}
break;
}
case ObjectTypes.Items:
{
if (!(targeted is Item))
{
from.SendMessage("This command only works on items.");
return;
}
break;
}
case ObjectTypes.Mobiles:
{
if (!(targeted is Mobile))
{
from.SendMessage("This command only works on mobiles.");
return;
}
break;
}
}
RunCommand(from, targeted, command, args);
from.BeginTarget(-1, command.ObjectTypes == ObjectTypes.All, TargetFlags.None,
(m, t) => OnTarget(m, t, command, args));
}
}
}

View file

@ -0,0 +1,249 @@
using System;
using System.Collections.Generic;
namespace Server.Commands.Generic
{
public sealed class ObjectConditional
{
private static readonly Type typeofItem = typeof(Item);
private static readonly Type typeofMobile = typeof(Mobile);
public static readonly ObjectConditional Empty = new ObjectConditional(null, null);
private IConditional[] m_Conditionals;
private ICondition[][] m_Conditions;
public ObjectConditional(Type objectType, ICondition[][] conditions)
{
Type = objectType;
m_Conditions = conditions;
}
public Type Type{ get; }
public bool IsItem => Type == null || Type == typeofItem || Type.IsSubclassOf(typeofItem);
public bool IsMobile => Type == null || Type == typeofMobile || Type.IsSubclassOf(typeofMobile);
public bool HasCompiled => m_Conditionals != null;
public void Compile(ref AssemblyEmitter emitter)
{
if (emitter == null)
emitter = new AssemblyEmitter("__dynamic");
m_Conditionals = new IConditional[m_Conditions.Length];
for (int i = 0; i < m_Conditionals.Length; ++i)
m_Conditionals[i] = ConditionalCompiler.Compile(emitter, Type, m_Conditions[i], i);
}
public bool CheckCondition(object obj)
{
if (Type == null)
return true; // null type means no condition
if (!HasCompiled)
{
AssemblyEmitter emitter = null;
Compile(ref emitter);
}
for (int i = 0; i < m_Conditionals.Length; ++i)
if (m_Conditionals[i].Verify(obj))
return true;
return false; // all conditions false
}
public static ObjectConditional Parse(Mobile from, ref string[] args)
{
string[] conditionArgs = null;
for (int i = 0; i < args.Length; ++i)
if (Insensitive.Equals(args[i], "where"))
{
string[] origArgs = args;
args = new string[i];
for (int j = 0; j < args.Length; ++j)
args[j] = origArgs[j];
conditionArgs = new string[origArgs.Length - i - 1];
for (int j = 0; j < conditionArgs.Length; ++j)
conditionArgs[j] = origArgs[i + j + 1];
break;
}
return ParseDirect(from, conditionArgs, 0, conditionArgs?.Length ?? 0);
}
public static ObjectConditional ParseDirect(Mobile from, string[] args, int offset, int size)
{
if (args == null || size == 0)
return Empty;
int index = 0;
Type objectType = ScriptCompiler.FindTypeByName(args[offset + index], true);
if (objectType == null)
throw new Exception($"No type with that name ({args[offset + index]}) was found.");
++index;
List<ICondition[]> conditions = new List<ICondition[]>();
List<ICondition> current = new List<ICondition>();
current.Add(TypeCondition.Default);
while (index < size)
{
string cur = args[offset + index];
bool inverse = false;
if (Insensitive.Equals(cur, "not") || cur == "!")
{
inverse = true;
++index;
if (index >= size)
throw new Exception("Improperly formatted object conditional.");
}
else if (Insensitive.Equals(cur, "or") || cur == "||")
{
if (current.Count > 1)
{
conditions.Add(current.ToArray());
current.Clear();
current.Add(TypeCondition.Default);
}
++index;
continue;
}
string binding = args[offset + index];
index++;
if (index >= size)
throw new Exception("Improperly formatted object conditional.");
string oper = args[offset + index];
index++;
if (index >= size)
throw new Exception("Improperly formatted object conditional.");
string val = args[offset + index];
index++;
Property prop = new Property(binding);
prop.BindTo(objectType, PropertyAccess.Read);
prop.CheckAccess(from);
ICondition condition = null;
switch (oper)
{
#region Equality
case "=":
case "==":
case "is":
condition = new ComparisonCondition(prop, inverse, ComparisonOperator.Equal, val);
break;
case "!=":
condition = new ComparisonCondition(prop, inverse, ComparisonOperator.NotEqual, val);
break;
#endregion
#region Relational
case ">":
condition = new ComparisonCondition(prop, inverse, ComparisonOperator.Greater, val);
break;
case "<":
condition = new ComparisonCondition(prop, inverse, ComparisonOperator.Lesser, val);
break;
case ">=":
condition = new ComparisonCondition(prop, inverse, ComparisonOperator.GreaterEqual, val);
break;
case "<=":
condition = new ComparisonCondition(prop, inverse, ComparisonOperator.LesserEqual, val);
break;
#endregion
#region Strings
case "==~":
case "~==":
case "=~":
case "~=":
case "is~":
case "~is":
condition = new StringCondition(prop, inverse, StringOperator.Equal, val, true);
break;
case "!=~":
case "~!=":
condition = new StringCondition(prop, inverse, StringOperator.NotEqual, val, true);
break;
case "starts":
condition = new StringCondition(prop, inverse, StringOperator.StartsWith, val, false);
break;
case "starts~":
case "~starts":
condition = new StringCondition(prop, inverse, StringOperator.StartsWith, val, true);
break;
case "ends":
condition = new StringCondition(prop, inverse, StringOperator.EndsWith, val, false);
break;
case "ends~":
case "~ends":
condition = new StringCondition(prop, inverse, StringOperator.EndsWith, val, true);
break;
case "contains":
condition = new StringCondition(prop, inverse, StringOperator.Contains, val, false);
break;
case "contains~":
case "~contains":
condition = new StringCondition(prop, inverse, StringOperator.Contains, val, true);
break;
#endregion
}
if (condition == null)
throw new InvalidOperationException($"Unrecognized operator (\"{oper}\").");
current.Add(condition);
}
conditions.Add(current.ToArray());
return new ObjectConditional(objectType, conditions.ToArray());
}
}
}

View file

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using Server.Network;
namespace Server.Commands.Generic
{
public class OnlineCommandImplementor : BaseCommandImplementor
{
public OnlineCommandImplementor()
{
Accessors = new[] { "Online" };
SupportRequirement = CommandSupport.Online;
SupportsConditionals = true;
AccessLevel = AccessLevel.GameMaster;
Usage = "Online <command> [condition]";
Description =
"Invokes the command on all mobiles that are currently logged in. Optional condition arguments can further restrict the set of objects.";
}
public override void Compile(Mobile from, BaseCommand command, ref string[] args, ref object obj)
{
try
{
Extensions ext = Extensions.Parse(from, ref args);
if (!CheckObjectTypes(from, command, ext, out bool _, out bool mobiles))
return;
if (!mobiles) // sanity check
{
command.LogFailure("This command does not support items.");
return;
}
List<object> list = new List<object>();
List<NetState> states = NetState.Instances;
for (int i = 0; i < states.Count; ++i)
{
NetState ns = states[i];
Mobile mob = ns.Mobile;
if (mob == null)
continue;
if (!BaseCommand.IsAccessible(from, mob))
continue;
if (ext.IsValid(mob))
list.Add(mob);
}
ext.Filter(list);
obj = list;
}
catch (Exception ex)
{
from.SendMessage(ex.Message);
}
}
}
}

View file

@ -0,0 +1,79 @@
namespace Server.Commands.Generic
{
public class RangeCommandImplementor : BaseCommandImplementor
{
public RangeCommandImplementor()
{
Accessors = new[] { "Range" };
SupportRequirement = CommandSupport.Area;
SupportsConditionals = true;
AccessLevel = AccessLevel.GameMaster;
Usage = "Range <range> <command> [condition]";
Description =
"Invokes the command on all appropriate objects within a specified range of you. Optional condition arguments can further restrict the set of objects.";
Instance = this;
}
public static RangeCommandImplementor Instance{ get; private set; }
public override void Execute(CommandEventArgs e)
{
if (e.Length >= 2)
{
int range = e.GetInt32(0);
if (range < 0)
{
e.Mobile.SendMessage("The range must not be negative.");
}
else
{
Commands.TryGetValue(e.GetString(1), out BaseCommand command);
if (command == null)
{
e.Mobile.SendMessage(
"That is either an invalid command name or one that does not support this modifier.");
}
else if (e.Mobile.AccessLevel < command.AccessLevel)
{
e.Mobile.SendMessage("You do not have access to that command.");
}
else
{
string[] oldArgs = e.Arguments;
string[] args = new string[oldArgs.Length - 2];
for (int i = 0; i < args.Length; ++i)
args[i] = oldArgs[i + 2];
Process(range, e.Mobile, command, args);
}
}
}
else
{
e.Mobile.SendMessage("You must supply a range and a command name.");
}
}
public void Process(int range, Mobile from, BaseCommand command, string[] args)
{
AreaCommandImplementor impl = AreaCommandImplementor.Instance;
if (impl == null)
return;
Map map = from.Map;
if (map == null || map == Map.Internal)
return;
Point3D start = new Point3D(from.X - range, from.Y - range, from.Z);
Point3D end = new Point3D(from.X + range, from.Y + range, from.Z);
impl.OnTarget(from, map, start, end, command, args);
}
}
}

View file

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
namespace Server.Commands.Generic
{
public class RegionCommandImplementor : BaseCommandImplementor
{
public RegionCommandImplementor()
{
Accessors = new[] { "Region" };
SupportRequirement = CommandSupport.Region;
SupportsConditionals = true;
AccessLevel = AccessLevel.GameMaster;
Usage = "Region <command> [condition]";
Description =
"Invokes the command on all appropriate mobiles in your current region. Optional condition arguments can further restrict the set of objects.";
}
public override void Compile(Mobile from, BaseCommand command, ref string[] args, ref object obj)
{
try
{
Extensions ext = Extensions.Parse(from, ref args);
if (!CheckObjectTypes(from, command, ext, out bool _, out bool mobiles))
return;
Region reg = from.Region;
List<object> list = new List<object>();
if (mobiles)
{
foreach (Mobile mob in reg.GetMobiles())
{
if (!BaseCommand.IsAccessible(from, mob))
continue;
if (ext.IsValid(mob))
list.Add(mob);
}
}
else
{
command.LogFailure("This command does not support items.");
return;
}
ext.Filter(list);
obj = list;
}
catch (Exception ex)
{
from.SendMessage(ex.Message);
}
}
}
}

View file

@ -0,0 +1,23 @@
namespace Server.Commands.Generic
{
public class ScreenCommandImplementor : BaseCommandImplementor
{
public ScreenCommandImplementor()
{
Accessors = new[] { "Screen" };
SupportRequirement = CommandSupport.Area;
SupportsConditionals = true;
AccessLevel = AccessLevel.GameMaster;
Usage = "Screen <command> [condition]";
Description =
"Invokes the command on all appropriate objects in your screen. Optional condition arguments can further restrict the set of objects.";
}
public override void Process(Mobile from, BaseCommand command, string[] args)
{
RangeCommandImplementor impl = RangeCommandImplementor.Instance;
impl?.Process(18, from, command, args);
}
}
}

View file

@ -0,0 +1,22 @@
namespace Server.Commands.Generic
{
public class SelfCommandImplementor : BaseCommandImplementor
{
public SelfCommandImplementor()
{
Accessors = new[] { "Self" };
SupportRequirement = CommandSupport.Self;
AccessLevel = AccessLevel.Counselor;
Usage = "Self <command>";
Description = "Invokes the command on the commanding player.";
}
public override void Compile(Mobile from, BaseCommand command, ref string[] args, ref object obj)
{
if (command.ObjectTypes == ObjectTypes.Items)
return; // sanity check
obj = from;
}
}
}

View file

@ -0,0 +1,96 @@
namespace Server.Commands.Generic
{
public class SerialCommandImplementor : BaseCommandImplementor
{
public SerialCommandImplementor()
{
Accessors = new[] { "Serial" };
SupportRequirement = CommandSupport.Single;
AccessLevel = AccessLevel.Counselor;
Usage = "Serial <serial> <command>";
Description = "Invokes the command on a single object by serial.";
}
public override void Execute(CommandEventArgs e)
{
if (e.Length >= 2)
{
Serial serial = e.GetUInt32(0);
object obj = null;
if (serial.IsItem)
obj = World.FindItem(serial);
else if (serial.IsMobile)
obj = World.FindMobile(serial);
if (obj == null)
{
e.Mobile.SendMessage("That is not a valid serial.");
}
else
{
Commands.TryGetValue(e.GetString(1), out BaseCommand command);
if (command == null)
{
e.Mobile.SendMessage(
"That is either an invalid command name or one that does not support this modifier.");
}
else if (e.Mobile.AccessLevel < command.AccessLevel)
{
e.Mobile.SendMessage("You do not have access to that command.");
}
else
{
switch (command.ObjectTypes)
{
// case ObjectTypes.Both:
// {
// if (!(obj is Item) && !(obj is Mobile))
// {
// e.Mobile.SendMessage("This command does not work on that.");
// return;
// }
//
// break;
// }
case ObjectTypes.Items:
{
if (!(obj is Item))
{
e.Mobile.SendMessage("This command only works on items.");
return;
}
break;
}
case ObjectTypes.Mobiles:
{
if (!(obj is Mobile))
{
e.Mobile.SendMessage("This command only works on mobiles.");
return;
}
break;
}
}
string[] oldArgs = e.Arguments;
string[] args = new string[oldArgs.Length - 2];
for (int i = 0; i < args.Length; ++i)
args[i] = oldArgs[i + 2];
RunCommand(e.Mobile, obj, command, args);
}
}
}
else
{
e.Mobile.SendMessage("You must supply an object serial and a command name.");
}
}
}
}

View file

@ -0,0 +1,89 @@
using Server.Targeting;
namespace Server.Commands.Generic
{
public class SingleCommandImplementor : BaseCommandImplementor
{
public SingleCommandImplementor()
{
Accessors = new[] { "Single" };
SupportRequirement = CommandSupport.Single;
AccessLevel = AccessLevel.Counselor;
Usage = "Single <command>";
Description =
"Invokes the command on a single targeted object. This is the same as just invoking the command directly.";
}
public override void Register(BaseCommand command)
{
base.Register(command);
for (int i = 0; i < command.Commands.Length; ++i)
CommandSystem.Register(command.Commands[i], command.AccessLevel, Redirect);
}
public void Redirect(CommandEventArgs e)
{
Commands.TryGetValue(e.Command, out BaseCommand command);
if (command == null)
e.Mobile.SendMessage("That is either an invalid command name or one that does not support this modifier.");
else if (e.Mobile.AccessLevel < command.AccessLevel)
e.Mobile.SendMessage("You do not have access to that command.");
else if (command.ValidateArgs(this, e))
Process(e.Mobile, command, e.Arguments);
}
public override void Process(Mobile from, BaseCommand command, string[] args)
{
if (command.ValidateArgs(this, new CommandEventArgs(from, command.Commands[0], GenerateArgString(args), args)))
from.BeginTarget(-1, command.ObjectTypes == ObjectTypes.All, TargetFlags.None,
(m, targeted) => OnTarget(m, targeted, command, args));
}
public void OnTarget(Mobile from, object targeted, BaseCommand command, string[] args)
{
if (!BaseCommand.IsAccessible(from, targeted))
{
from.SendLocalizedMessage(500447); // That is not accessible.
return;
}
switch (command.ObjectTypes)
{
case ObjectTypes.Both:
{
if (!(targeted is Item) && !(targeted is Mobile))
{
from.SendMessage("This command does not work on that.");
return;
}
break;
}
case ObjectTypes.Items:
{
if (!(targeted is Item))
{
from.SendMessage("This command only works on items.");
return;
}
break;
}
case ObjectTypes.Mobiles:
{
if (!(targeted is Mobile))
{
from.SendMessage("This command only works on mobiles.");
return;
}
break;
}
}
RunCommand(from, targeted, command, args);
}
}
}

View file

@ -0,0 +1,962 @@
using System.Collections.Generic;
using System.Text;
using Server.Commands.Generic;
using Server.Engines.Help;
using Server.Gumps;
using Server.Items;
using Server.Menus.ItemLists;
using Server.Menus.Questions;
using Server.Misc;
using Server.Mobiles;
using Server.Multis;
using Server.Network;
using Server.Spells;
using Server.Targeting;
using Server.Targets;
namespace Server.Commands
{
public class CommandHandlers
{
public static void Initialize()
{
CommandSystem.Prefix = "[";
Register("Go", AccessLevel.Counselor, Go_OnCommand);
Register("DropHolding", AccessLevel.Counselor, DropHolding_OnCommand);
Register("GetFollowers", AccessLevel.GameMaster, GetFollowers_OnCommand);
Register("ClearFacet", AccessLevel.Administrator, ClearFacet_OnCommand);
Register("Where", AccessLevel.Counselor, Where_OnCommand);
Register("AutoPageNotify", AccessLevel.Counselor, APN_OnCommand);
Register("APN", AccessLevel.Counselor, APN_OnCommand);
Register("Animate", AccessLevel.GameMaster, Animate_OnCommand);
Register("Cast", AccessLevel.Counselor, Cast_OnCommand);
Register("Stuck", AccessLevel.Counselor, Stuck_OnCommand);
Register("Help", AccessLevel.Player, Help_OnCommand);
Register("Save", AccessLevel.Administrator, Save_OnCommand);
Register("BackgroundSave", AccessLevel.Administrator, BackgroundSave_OnCommand);
Register("BGSave", AccessLevel.Administrator, BackgroundSave_OnCommand);
Register("SaveBG", AccessLevel.Administrator, BackgroundSave_OnCommand);
Register("Move", AccessLevel.GameMaster, Move_OnCommand);
Register("Client", AccessLevel.Counselor, Client_OnCommand);
Register("SMsg", AccessLevel.Counselor, StaffMessage_OnCommand);
Register("SM", AccessLevel.Counselor, StaffMessage_OnCommand);
Register("S", AccessLevel.Counselor, StaffMessage_OnCommand);
Register("BCast", AccessLevel.GameMaster, BroadcastMessage_OnCommand);
Register("BC", AccessLevel.GameMaster, BroadcastMessage_OnCommand);
Register("B", AccessLevel.GameMaster, BroadcastMessage_OnCommand);
Register("Bank", AccessLevel.GameMaster, Bank_OnCommand);
Register("Echo", AccessLevel.Counselor, Echo_OnCommand);
Register("Sound", AccessLevel.GameMaster, Sound_OnCommand);
Register("ViewEquip", AccessLevel.GameMaster, ViewEquip_OnCommand);
Register("Light", AccessLevel.Counselor, Light_OnCommand);
Register("Stats", AccessLevel.Counselor, Stats_OnCommand);
Register("SpeedBoost", AccessLevel.Counselor, SpeedBoost_OnCommand);
}
public static void Register(string command, AccessLevel access, CommandEventHandler handler)
{
CommandSystem.Register(command, access, handler);
}
[Usage("SpeedBoost [true|false]")]
[Description("Enables a speed boost for the invoker. Disable with parameters.")]
private static void SpeedBoost_OnCommand(CommandEventArgs e)
{
Mobile from = e.Mobile;
if (e.Length <= 1)
{
if (e.Length == 1 && !e.GetBoolean(0))
{
from.Send(SpeedControl.Disable);
from.SendMessage("Speed boost has been disabled.");
}
else
{
from.Send(SpeedControl.MountSpeed);
from.SendMessage("Speed boost has been enabled.");
}
}
else
{
from.SendMessage("Format: SpeedBoost [true|false]");
}
}
[Usage("Where")]
[Description("Tells the commanding player his coordinates, region, and facet.")]
public static void Where_OnCommand(CommandEventArgs e)
{
Mobile from = e.Mobile;
Map map = from.Map;
from.SendMessage("You are at {0} {1} {2} in {3}.", from.X, from.Y, from.Z, map);
if (map != null)
{
Region reg = from.Region;
if (!reg.IsDefault)
{
StringBuilder builder = new StringBuilder();
builder.Append(reg);
reg = reg.Parent;
while (reg != null)
{
builder.Append(" <- " + reg);
reg = reg.Parent;
}
from.SendMessage("Your region is {0}.", builder.ToString());
}
}
}
[Usage("DropHolding")]
[Description(
"Drops the item, if any, that a targeted player is holding. The item is placed into their backpack, or if that's full, at their feet.")]
public static void DropHolding_OnCommand(CommandEventArgs e)
{
e.Mobile.BeginTarget(-1, false, TargetFlags.None, DropHolding_OnTarget);
e.Mobile.SendMessage("Target the player to drop what they are holding.");
}
public static void DropHolding_OnTarget(Mobile from, object obj)
{
if (obj is Mobile targ && targ.Player)
{
Item held = targ.Holding;
if (held == null)
{
from.SendMessage("They are not holding anything.");
}
else
{
if (from.AccessLevel == AccessLevel.Counselor)
{
PageEntry pe = PageQueue.GetEntry(targ);
if (pe?.Handler == from)
from.SendMessage("You may only use this command if you are handling their help page.");
else
from.SendMessage("You may only use this command on someone who has paged you.");
return;
}
if (targ.AddToBackpack(held))
from.SendMessage("The item they were holding has been placed into their backpack.");
else
from.SendMessage("The item they were holding has been placed at their feet.");
held.ClearBounce();
targ.Holding = null;
}
}
else
{
from.BeginTarget(-1, false, TargetFlags.None, DropHolding_OnTarget);
from.SendMessage("That is not a player. Try again.");
}
}
public static void DeleteList_Callback(Mobile from, bool okay, List<IEntity> list)
{
if (okay)
{
CommandLogging.WriteLine(from, "{0} {1} deleting {2} object{3}", from.AccessLevel,
CommandLogging.Format(from), list.Count, list.Count == 1 ? "" : "s");
NetState.Pause();
for (int i = 0; i < list.Count; ++i)
list[i].Delete();
NetState.Resume();
from.SendMessage("You have deleted {0} object{1}.", list.Count, list.Count == 1 ? "" : "s");
}
else
{
from.SendMessage("You have chosen not to delete those objects.");
}
}
[Usage("ClearFacet")]
[Description("Deletes all items and mobiles in your facet. Players and their inventory will not be deleted.")]
public static void ClearFacet_OnCommand(CommandEventArgs e)
{
Mobile from = e.Mobile;
Map map = from.Map;
if (map == null || map == Map.Internal)
{
from.SendMessage("You may not run that command here.");
return;
}
List<IEntity> list = new List<IEntity>();
foreach (Item item in World.Items.Values)
if (item.Map == map && item.Parent == null)
list.Add(item);
foreach (Mobile m in World.Mobiles.Values)
if (m.Map == map && !m.Player)
list.Add(m);
if (list.Count > 0)
{
CommandLogging.WriteLine(from, "{0} {1} starting facet clear of {2} ({3} object{4})",
from.AccessLevel, CommandLogging.Format(from), map, list.Count, list.Count == 1 ? "" : "s");
from.SendGump(
new WarningGump(1060635, 30720,
$"You are about to delete {list.Count} object{(list.Count == 1 ? "" : "s")} from this facet. Do you really wish to continue?",
0xFFC000, 360, 260, okay => DeleteList_Callback(from, okay, list)));
}
else
{
from.SendMessage("There were no objects found to delete.");
}
}
[Usage("GetFollowers")]
[Description("Teleports all pets of a targeted player to your location.")]
public static void GetFollowers_OnCommand(CommandEventArgs e)
{
e.Mobile.BeginTarget(-1, false, TargetFlags.None, GetFollowers_OnTarget);
e.Mobile.SendMessage("Target a player to get their pets.");
}
public static void GetFollowers_OnTarget(Mobile from, object obj)
{
if (obj is PlayerMobile pm)
{
List<Mobile> pets = pm.AllFollowers;
if (pets.Count > 0)
{
CommandLogging.WriteLine(from, "{0} {1} getting all followers of {2}", from.AccessLevel,
CommandLogging.Format(from), CommandLogging.Format(pm));
from.SendMessage("That player has {0} pet{1}.", pets.Count, pets.Count != 1 ? "s" : "");
for (int i = 0; i < pets.Count; ++i)
{
Mobile pet = pets[i];
if (pet is IMount mount)
mount.Rider = null; // make sure it's dismounted
pet.MoveToWorld(from.Location, from.Map);
}
}
else
{
from.SendMessage("There were no pets found for that player.");
}
}
else if (obj is Mobile master && master.Player)
{
List<BaseCreature> pets = new List<BaseCreature>();
foreach (Mobile m in World.Mobiles.Values)
if (m is BaseCreature bc)
if (bc.Controlled && bc.ControlMaster == master || bc.Summoned && bc.SummonMaster == master)
pets.Add(bc);
if (pets.Count > 0)
{
CommandLogging.WriteLine(from, "{0} {1} getting all followers of {2}", from.AccessLevel,
CommandLogging.Format(from), CommandLogging.Format(master));
from.SendMessage("That player has {0} pet{1}.", pets.Count, pets.Count != 1 ? "s" : "");
for (int i = 0; i < pets.Count; ++i)
{
Mobile pet = pets[i];
if (pet is IMount mount)
mount.Rider = null; // make sure it's dismounted
pet.MoveToWorld(from.Location, from.Map);
}
}
else
{
from.SendMessage("There were no pets found for that player.");
}
}
else
{
from.BeginTarget(-1, false, TargetFlags.None, GetFollowers_OnTarget);
from.SendMessage("That is not a player. Try again.");
}
}
[Usage("ViewEquip")]
[Description("Lists equipment of a targeted mobile. From the list you can move, delete, or open props.")]
public static void ViewEquip_OnCommand(CommandEventArgs e)
{
e.Mobile.Target = new ViewEqTarget();
}
[Usage("Sound <index> [toAll=true]")]
[Description(
"Plays a sound to players within 12 tiles of you. The (toAll) argument specifies to everyone, or just those who can see you.")]
public static void Sound_OnCommand(CommandEventArgs e)
{
if (e.Length == 1)
PlaySound(e.Mobile, e.GetInt32(0), true);
else if (e.Length == 2)
PlaySound(e.Mobile, e.GetInt32(0), e.GetBoolean(1));
else
e.Mobile.SendMessage("Format: Sound <index> [toAll]");
}
private static void PlaySound(Mobile m, int index, bool toAll)
{
Map map = m.Map;
if (map == null)
return;
CommandLogging.WriteLine(m, "{0} {1} playing sound {2} (toAll={3})", m.AccessLevel, CommandLogging.Format(m),
index, toAll);
Packet p = new PlaySound(index, m.Location);
p.Acquire();
foreach (NetState state in m.GetClientsInRange(12))
if (toAll || state.Mobile.CanSee(m))
state.Send(p);
p.Release();
}
[Usage("Echo <text>")]
[Description("Relays (text) as a system message.")]
public static void Echo_OnCommand(CommandEventArgs e)
{
string toEcho = e.ArgString.Trim();
if (toEcho.Length > 0)
e.Mobile.SendMessage(toEcho);
else
e.Mobile.SendMessage("Format: Echo \"<text>\"");
}
[Usage("Bank")]
[Description("Opens the bank box of a given target.")]
public static void Bank_OnCommand(CommandEventArgs e)
{
e.Mobile.Target = new BankTarget();
}
[Usage("Client")]
[Description("Opens the client gump menu for a given player.")]
private static void Client_OnCommand(CommandEventArgs e)
{
e.Mobile.Target = new ClientTarget();
}
[Usage("Move")]
[Description("Repositions a targeted item or mobile.")]
private static void Move_OnCommand(CommandEventArgs e)
{
e.Mobile.Target = new PickMoveTarget();
}
[Usage("Save")]
[Description("Saves the world.")]
private static void Save_OnCommand(CommandEventArgs e)
{
AutoSave.Save();
}
[Usage("BackgroundSave")]
[Aliases("BGSave", "SaveBG")]
[Description("Saves the world, writing to the disk in the background")]
private static void BackgroundSave_OnCommand(CommandEventArgs e)
{
AutoSave.Save(true);
}
private static bool FixMap(ref Map map, ref Point3D loc, Item item)
{
return map != null && map != Map.Internal || item.RootParent is Mobile m && FixMap(ref map, ref loc, m);
}
private static bool FixMap(ref Map map, ref Point3D loc, Mobile m)
{
bool validMap = map != null && map != Map.Internal;
if (!validMap)
{
map = m.LogoutMap;
loc = m.LogoutLocation;
}
return validMap;
}
[Usage("Go [name | serial | (x y [z]) | (deg min (N | S) deg min (E | W))]")]
[Description(
"With no arguments, this command brings up the go menu. With one argument, (name), you are moved to that regions \"go location.\" Or, if a numerical value is specified for one argument, (serial), you are moved to that object. Two or three arguments, (x y [z]), will move your character to that location. When six arguments are specified, (deg min (N | S) deg min (E | W)), your character will go to an approximate of those sextant coordinates.")]
private static void Go_OnCommand(CommandEventArgs e)
{
Mobile from = e.Mobile;
if (e.Length == 0)
{
GoGump.DisplayTo(from);
return;
}
if (e.Length == 1)
{
try
{
uint ser = e.GetUInt32(0);
IEntity ent = World.FindEntity(ser);
if (ent is Item item)
{
Map map = item.Map;
Point3D loc = item.GetWorldLocation();
Mobile owner = item.RootParent as Mobile;
if (owner?.Map != null && owner.Map != Map.Internal &&
!BaseCommand.IsAccessible(from, owner) /* !from.CanSee( owner )*/)
{
from.SendMessage("You can not go to what you can not see.");
return;
}
if (owner != null && (owner.Map == null || owner.Map == Map.Internal) && owner.Hidden &&
owner.AccessLevel >= from.AccessLevel)
{
from.SendMessage("You can not go to what you can not see.");
return;
}
if (!FixMap(ref map, ref loc, item))
{
from.SendMessage("That is an internal item and you cannot go to it.");
return;
}
from.MoveToWorld(loc, map);
return;
}
if (ent is Mobile m)
{
Map map = m.Map;
Point3D loc = m.Location;
Mobile owner = m;
if (owner.Map != null && owner.Map != Map.Internal &&
!BaseCommand.IsAccessible(from, owner) /* !from.CanSee( owner )*/)
{
from.SendMessage("You can not go to what you can not see.");
return;
}
if ((owner.Map == null || owner.Map == Map.Internal) && owner.Hidden &&
owner.AccessLevel >= from.AccessLevel)
{
from.SendMessage("You can not go to what you can not see.");
return;
}
if (!FixMap(ref map, ref loc, m))
{
from.SendMessage("That is an internal mobile and you cannot go to it.");
return;
}
from.MoveToWorld(loc, map);
return;
}
else
{
string name = e.GetString(0);
Map map;
for (int i = 0; i < Map.AllMaps.Count; ++i)
{
map = Map.AllMaps[i];
if (map.MapIndex == 0x7F || map.MapIndex == 0xFF)
continue;
if (Insensitive.Equals(name, map.Name))
{
from.Map = map;
return;
}
}
Dictionary<string, Region> list = from.Map.Regions;
foreach (KeyValuePair<string, Region> kvp in list)
{
Region r = kvp.Value;
if (Insensitive.Equals(r.Name, name))
{
from.Location = new Point3D(r.GoLocation);
return;
}
}
for (int i = 0; i < Map.AllMaps.Count; ++i)
{
map = Map.AllMaps[i];
if (map.MapIndex == 0x7F || map.MapIndex == 0xFF || from.Map == map)
continue;
foreach (Region r in map.Regions.Values)
if (Insensitive.Equals(r.Name, name))
{
from.MoveToWorld(r.GoLocation, map);
return;
}
}
if (ser != 0)
from.SendMessage("No object with that serial was found.");
else
from.SendMessage("No region with that name was found.");
return;
}
}
catch
{
// ignored
}
from.SendMessage("Region name not found");
}
else if (e.Length == 2 || e.Length == 3)
{
Map map = from.Map;
if (map != null)
try
{
/*
* This to avoid being teleported to (0,0) if trying to teleport
* to a region with spaces in its name.
*/
int x = int.Parse(e.GetString(0));
int y = int.Parse(e.GetString(1));
int z = e.Length == 3 ? int.Parse(e.GetString(2)) : map.GetAverageZ(x, y);
from.Location = new Point3D(x, y, z);
}
catch
{
from.SendMessage("Region name not found.");
}
}
else if (e.Length == 6)
{
Map map = from.Map;
if (map != null)
{
Point3D p = Sextant.ReverseLookup(map, e.GetInt32(3), e.GetInt32(0), e.GetInt32(4), e.GetInt32(1),
Insensitive.Equals(e.GetString(5), "E"), Insensitive.Equals(e.GetString(2), "S"));
if (p != Point3D.Zero)
from.Location = p;
else
from.SendMessage("Sextant reverse lookup failed.");
}
}
else
{
from.SendMessage("Format: Go [name | serial | (x y [z]) | (deg min (N | S) deg min (E | W)]");
}
}
[Usage("Help")]
[Description("Lists all available commands.")]
public static void Help_OnCommand(CommandEventArgs e)
{
Mobile m = e.Mobile;
List<CommandEntry> list = new List<CommandEntry>();
foreach (CommandEntry entry in CommandSystem.Entries.Values)
if (m.AccessLevel >= entry.AccessLevel)
list.Add(entry);
list.Sort();
StringBuilder sb = new StringBuilder();
if (list.Count > 0)
sb.Append(list[0].Command);
for (int i = 1; i < list.Count; ++i)
{
string v = list[i].Command;
if (sb.Length + 1 + v.Length >= 256)
{
m.SendAsciiMessage(0x482, sb.ToString());
sb = new StringBuilder();
sb.Append(v);
}
else
{
sb.Append(' ');
sb.Append(v);
}
}
if (sb.Length > 0)
m.SendAsciiMessage(0x482, sb.ToString());
}
[Usage("SMsg <text>")]
[Aliases("S", "SM")]
[Description("Broadcasts a message to all online staff.")]
public static void StaffMessage_OnCommand(CommandEventArgs e)
{
BroadcastMessage(AccessLevel.Counselor, e.Mobile.SpeechHue, $"[{e.Mobile.Name}] {e.ArgString}");
}
[Usage("BCast <text>")]
[Aliases("B", "BC")]
[Description("Broadcasts a message to everyone online.")]
public static void BroadcastMessage_OnCommand(CommandEventArgs e)
{
BroadcastMessage(AccessLevel.Player, 0x482, $"Staff message from {e.Mobile.Name}:");
BroadcastMessage(AccessLevel.Player, 0x482, e.ArgString);
}
public static void BroadcastMessage(AccessLevel ac, int hue, string message)
{
foreach (NetState state in NetState.Instances)
{
Mobile m = state.Mobile;
if (m?.AccessLevel >= ac)
m.SendMessage(hue, message);
}
}
[Usage("AutoPageNotify")]
[Aliases("APN")]
[Description("Toggles your auto-page-notify status.")]
public static void APN_OnCommand(CommandEventArgs e)
{
Mobile m = e.Mobile;
m.AutoPageNotify = !m.AutoPageNotify;
m.SendMessage("Your auto-page-notify has been turned {0}.", m.AutoPageNotify ? "on" : "off");
}
[Usage("Animate <action> <frameCount> <repeatCount> <forward> <repeat> <delay>")]
[Description("Makes your character do a specified animation.")]
public static void Animate_OnCommand(CommandEventArgs e)
{
if (e.Length == 6)
e.Mobile.Animate(e.GetInt32(0), e.GetInt32(1), e.GetInt32(2), e.GetBoolean(3), e.GetBoolean(4),
e.GetInt32(5));
else
e.Mobile.SendMessage("Format: Animate <action> <frameCount> <repeatCount> <forward> <repeat> <delay>");
}
[Usage("Cast <name>")]
[Description("Casts a spell by name.")]
public static void Cast_OnCommand(CommandEventArgs e)
{
if (e.Length == 1)
{
if (!DesignContext.Check(e.Mobile))
return; // They are customizing
Spell spell = SpellRegistry.NewSpell(e.GetString(0), e.Mobile, null);
if (spell != null)
spell.Cast();
else
e.Mobile.SendMessage("That spell was not found.");
}
else
{
e.Mobile.SendMessage("Format: Cast <name>");
}
}
[Usage("Stuck")]
[Description("Opens a menu of towns, used for teleporting stuck mobiles.")]
public static void Stuck_OnCommand(CommandEventArgs e)
{
e.Mobile.Target = new StuckMenuTarget();
}
[Usage("Light <level>")]
[Description("Set your local lightlevel.")]
public static void Light_OnCommand(CommandEventArgs e)
{
e.Mobile.LightLevel = e.GetInt32(0);
}
[Usage("Stats")]
[Description("View some stats about the server.")]
public static void Stats_OnCommand(CommandEventArgs e)
{
e.Mobile.SendMessage("Open Connections: {0}", NetState.Instances.Count);
e.Mobile.SendMessage("Mobiles: {0}", World.Mobiles.Count);
e.Mobile.SendMessage("Items: {0}", World.Items.Count);
}
private class ViewEqTarget : Target
{
public ViewEqTarget() : base(-1, false, TargetFlags.None)
{
}
protected override void OnTarget(Mobile from, object targeted)
{
if (!BaseCommand.IsAccessible(from, targeted))
{
from.SendLocalizedMessage(500447); // That is not accessible.
return;
}
if (targeted is Mobile mobile)
from.SendMenu(new EquipMenu(from, mobile, GetEquip(mobile)));
}
private static ItemListEntry[] GetEquip(Mobile m)
{
ItemListEntry[] entries = new ItemListEntry[m.Items.Count];
for (int i = 0; i < m.Items.Count; ++i)
{
Item item = m.Items[i];
entries[i] = new ItemListEntry($"{item.Layer}: {item.GetType().Name}", item.ItemID, item.Hue);
}
return entries;
}
private class EquipMenu : ItemListMenu
{
private Mobile m_Mobile;
public EquipMenu(Mobile from, Mobile m, ItemListEntry[] entries) : base("Equipment", entries)
{
m_Mobile = m;
CommandLogging.WriteLine(from, "{0} {1} viewing equipment of {2}", from.AccessLevel,
CommandLogging.Format(from), CommandLogging.Format(m));
}
public override void OnResponse(NetState state, int index)
{
if (index >= 0 && index < m_Mobile.Items.Count)
{
Item item = m_Mobile.Items[index];
state.Mobile.SendMenu(new EquipDetailsMenu(m_Mobile, item));
}
}
private class EquipDetailsMenu : QuestionMenu
{
private Item m_Item;
private Mobile m_Mobile;
public EquipDetailsMenu(Mobile m, Item item) : base($"{item.Layer}: {item.GetType().Name}",
new[] { "Move", "Delete", "Props" })
{
m_Mobile = m;
m_Item = item;
}
public override void OnCancel(NetState state)
{
state.Mobile.SendMenu(new EquipMenu(state.Mobile, m_Mobile, GetEquip(m_Mobile)));
}
public override void OnResponse(NetState state, int index)
{
if (index == 0)
{
CommandLogging.WriteLine(state.Mobile, "{0} {1} moving equipment item {2} of {3}",
state.Mobile.AccessLevel, CommandLogging.Format(state.Mobile), CommandLogging.Format(m_Item),
CommandLogging.Format(m_Mobile));
state.Mobile.Target = new MoveTarget(m_Item);
}
else if (index == 1)
{
CommandLogging.WriteLine(state.Mobile, "{0} {1} deleting equipment item {2} of {3}",
state.Mobile.AccessLevel, CommandLogging.Format(state.Mobile), CommandLogging.Format(m_Item),
CommandLogging.Format(m_Mobile));
m_Item.Delete();
}
else if (index == 2)
{
CommandLogging.WriteLine(state.Mobile,
"{0} {1} opening properties for equipment item {2} of {3}", state.Mobile.AccessLevel,
CommandLogging.Format(state.Mobile), CommandLogging.Format(m_Item),
CommandLogging.Format(m_Mobile));
state.Mobile.SendGump(new PropertiesGump(state.Mobile, m_Item));
}
}
}
}
}
private class BankTarget : Target
{
public BankTarget() : base(-1, false, TargetFlags.None)
{
}
protected override void OnTarget(Mobile from, object targeted)
{
if (targeted is Mobile m)
{
BankBox box = m.Player ? m.BankBox : m.FindBankNoCreate();
if (box != null)
{
CommandLogging.WriteLine(from, "{0} {1} opening bank box of {2}", from.AccessLevel,
CommandLogging.Format(from), CommandLogging.Format(m));
if (from == m)
box.Open();
else
box.DisplayTo(from);
}
else
{
from.SendMessage("They have no bank box.");
}
}
}
}
private class DismountTarget : Target
{
public DismountTarget() : base(-1, false, TargetFlags.None)
{
}
protected override void OnTarget(Mobile from, object targeted)
{
if (targeted is Mobile targ)
{
CommandLogging.WriteLine(from, "{0} {1} dismounting {2}", from.AccessLevel, CommandLogging.Format(from),
CommandLogging.Format(targ));
for (int i = 0; i < targ.Items.Count; ++i)
{
Item item = targ.Items[i];
if (item is IMountItem mountItem)
{
IMount mount = mountItem.Mount;
if (mount != null)
mount.Rider = null;
if (targ.Items.IndexOf(item) == -1)
--i;
}
}
for (int i = 0; i < targ.Items.Count; ++i)
{
Item item = targ.Items[i];
if (item.Layer == Layer.Mount)
{
item.Delete();
--i;
}
}
}
}
}
private class ClientTarget : Target
{
public ClientTarget() : base(-1, false, TargetFlags.None)
{
}
protected override void OnTarget(Mobile from, object targeted)
{
if (targeted is Mobile targ && targ.NetState != null)
{
CommandLogging.WriteLine(from, "{0} {1} opening client menu of {2}", from.AccessLevel,
CommandLogging.Format(from), CommandLogging.Format(targ));
from.SendGump(new ClientGump(from, targ.NetState));
}
}
}
private class StuckMenuTarget : Target
{
public StuckMenuTarget() : base(-1, false, TargetFlags.None)
{
}
protected override void OnTarget(Mobile from, object targeted)
{
if (targeted is Mobile mobile)
{
if (mobile.AccessLevel >= from.AccessLevel && mobile != from)
from.SendMessage("You can't do that to someone with higher Accesslevel than you!");
else
from.SendGump(new StuckMenu(from, mobile, false));
}
}
}
}
}

View file

@ -0,0 +1,402 @@
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Server.Commands.Generic;
using Server.Gumps;
using Server.Network;
using CommandInfo = Server.Commands.Docs.DocCommandEntry;
using CommandInfoSorter = Server.Commands.Docs.CommandEntrySorter;
namespace Server.Commands
{
public class HelpInfo
{
public static Dictionary<string, CommandInfo> HelpInfos{ get; } = new Dictionary<string, CommandInfo>();
public static List<CommandInfo> SortedHelpInfo{ get; private set; } = new List<CommandInfo>();
[CallPriority(100)]
public static void Initialize()
{
CommandSystem.Register("HelpInfo", AccessLevel.Player, HelpInfo_OnCommand);
FillTable();
}
[Usage("HelpInfo [<command>]")]
[Description(
"Gives information on a specified command, or when no argument specified, displays a gump containing all commands")]
private static void HelpInfo_OnCommand(CommandEventArgs e)
{
if (e.Length > 0)
{
string arg = e.GetString(0).ToLower();
if (HelpInfos.TryGetValue(arg, out CommandInfo c))
{
Mobile m = e.Mobile;
if (m.AccessLevel >= c.AccessLevel)
m.SendGump(new CommandInfoGump(c));
else
m.SendMessage("You don't have access to that command.");
return;
}
e.Mobile.SendMessage($"Command '{arg}' not found!");
}
e.Mobile.SendGump(new CommandListGump(0, e.Mobile, null));
}
public static void FillTable()
{
List<CommandEntry> commands = new List<CommandEntry>(CommandSystem.Entries.Values);
List<CommandInfo> list = new List<CommandInfo>();
commands.Sort();
commands.Reverse();
Docs.Clean(commands);
for (int i = 0; i < commands.Count; ++i)
{
CommandEntry e = commands[i];
MethodInfo mi = e.Handler.Method;
object[] attrs = mi.GetCustomAttributes(typeof(UsageAttribute), false);
if (attrs.Length == 0)
continue;
UsageAttribute usage = attrs[0] as UsageAttribute;
attrs = mi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attrs.Length == 0)
continue;
if (usage == null || !(attrs[0] is DescriptionAttribute desc))
continue;
attrs = mi.GetCustomAttributes(typeof(AliasesAttribute), false);
AliasesAttribute aliases = attrs.Length == 0 ? null : attrs[0] as AliasesAttribute;
string descString = desc.Description.Replace("<", "(").Replace(">", ")");
if (aliases == null)
{
list.Add(new CommandInfo(e.AccessLevel, e.Command, null, usage.Usage, descString));
}
else
{
list.Add(new CommandInfo(e.AccessLevel, e.Command, aliases.Aliases, usage.Usage, descString));
for (int j = 0; j < aliases.Aliases.Length; j++)
{
string[] newAliases = new string[aliases.Aliases.Length];
aliases.Aliases.CopyTo(newAliases, 0);
newAliases[j] = e.Command;
list.Add(new CommandInfo(e.AccessLevel, aliases.Aliases[j], newAliases, usage.Usage, descString));
}
}
}
for (int i = 0; i < TargetCommands.AllCommands.Count; ++i)
{
BaseCommand command = TargetCommands.AllCommands[i];
string usage = command.Usage;
string desc = command.Description;
if (usage == null || desc == null)
continue;
string[] cmds = command.Commands;
string cmd = cmds[0];
string[] aliases = new string[cmds.Length - 1];
for (int j = 0; j < aliases.Length; ++j)
aliases[j] = cmds[j + 1];
desc = desc.Replace("<", "(").Replace(">", ")");
if (command.Supports != CommandSupport.Single)
{
StringBuilder sb = new StringBuilder(50 + desc.Length);
sb.Append("Modifiers: ");
if ((command.Supports & CommandSupport.Global) != 0)
sb.Append("<i>Global</i>, ");
if ((command.Supports & CommandSupport.Online) != 0)
sb.Append("<i>Online</i>, ");
if ((command.Supports & CommandSupport.Region) != 0)
sb.Append("<i>Region</i>, ");
if ((command.Supports & CommandSupport.Contained) != 0)
sb.Append("<i>Contained</i>, ");
if ((command.Supports & CommandSupport.Multi) != 0)
sb.Append("<i>Multi</i>, ");
if ((command.Supports & CommandSupport.Area) != 0)
sb.Append("<i>Area</i>, ");
if ((command.Supports & CommandSupport.Self) != 0)
sb.Append("<i>Self</i>, ");
sb.Remove(sb.Length - 2, 2);
sb.Append("<br>");
sb.Append(desc);
desc = sb.ToString();
}
list.Add(new CommandInfo(command.AccessLevel, cmd, aliases, usage, desc));
for (int j = 0; j < aliases.Length; j++)
{
string[] newAliases = new string[aliases.Length];
aliases.CopyTo(newAliases, 0);
newAliases[j] = cmd;
list.Add(new CommandInfo(command.AccessLevel, aliases[j], newAliases, usage, desc));
}
}
List<BaseCommandImplementor> commandImpls = BaseCommandImplementor.Implementors;
for (int i = 0; i < commandImpls.Count; ++i)
{
BaseCommandImplementor command = commandImpls[i];
string usage = command.Usage;
string desc = command.Description;
if (usage == null || desc == null)
continue;
string[] cmds = command.Accessors;
string cmd = cmds[0];
string[] aliases = new string[cmds.Length - 1];
for (int j = 0; j < aliases.Length; ++j)
aliases[j] = cmds[j + 1];
desc = desc.Replace("<", ")").Replace(">", ")");
list.Add(new CommandInfo(command.AccessLevel, cmd, aliases, usage, desc));
for (int j = 0; j < aliases.Length; j++)
{
string[] newAliases = new string[aliases.Length];
aliases.CopyTo(newAliases, 0);
newAliases[j] = cmd;
list.Add(new CommandInfo(command.AccessLevel, aliases[j], newAliases, usage, desc));
}
}
list.Sort(new CommandInfoSorter());
SortedHelpInfo = list;
foreach (CommandInfo c in SortedHelpInfo)
if (!HelpInfos.ContainsKey(c.Name.ToLower()))
HelpInfos.Add(c.Name.ToLower(), c);
}
public class CommandListGump : BaseGridGump
{
private const int EntriesPerPage = 15;
private List<CommandInfo> m_List;
private int m_Page;
public CommandListGump(int page, Mobile from, List<CommandInfo> list)
: base(30, 30)
{
m_Page = page;
if (list == null)
{
m_List = new List<CommandInfo>();
foreach (CommandInfo c in SortedHelpInfo)
if (from.AccessLevel >= c.AccessLevel)
m_List.Add(c);
}
else
{
m_List = list;
}
AddNewPage();
if (m_Page > 0)
AddEntryButton(20, ArrowLeftID1, ArrowLeftID2, 1, ArrowLeftWidth, ArrowLeftHeight);
else
AddEntryHeader(20);
AddEntryHtml(160, Center(
$"Page {m_Page + 1} of {(m_List.Count + EntriesPerPage - 1) / EntriesPerPage}"));
if ((m_Page + 1) * EntriesPerPage < m_List.Count)
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 2, ArrowRightWidth, ArrowRightHeight);
else
AddEntryHeader(20);
int last = (int)AccessLevel.Player - 1;
for (int i = m_Page * EntriesPerPage, line = 0; line < EntriesPerPage && i < m_List.Count; ++i, ++line)
{
CommandInfo c = m_List[i];
if (from.AccessLevel >= c.AccessLevel)
{
if ((int)c.AccessLevel != last)
{
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, Color(c.AccessLevel.ToString(), 0xFF0000));
AddEntryHeader(20);
line++;
}
last = (int)c.AccessLevel;
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, c.Name);
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 3 + i, ArrowRightWidth, ArrowRightHeight);
}
}
FinishPage();
}
public override void OnResponse(NetState sender, RelayInfo info)
{
Mobile m = sender.Mobile;
switch (info.ButtonID)
{
case 0:
{
m.CloseGump<CommandInfoGump>();
break;
}
case 1:
{
if (m_Page > 0)
m.SendGump(new CommandListGump(m_Page - 1, m, m_List));
break;
}
case 2:
{
if ((m_Page + 1) * EntriesPerPage < SortedHelpInfo.Count)
m.SendGump(new CommandListGump(m_Page + 1, m, m_List));
break;
}
default:
{
int v = info.ButtonID - 3;
if (v >= 0 && v < m_List.Count)
{
CommandInfo c = m_List[v];
if (m.AccessLevel >= c.AccessLevel)
{
m.SendGump(new CommandInfoGump(c));
m.SendGump(new CommandListGump(m_Page, m, m_List));
}
else
{
m.SendMessage("You no longer have access to that command.");
m.SendGump(new CommandListGump(m_Page, m, null));
}
}
break;
}
}
}
}
public class CommandInfoGump : Gump
{
public CommandInfoGump(CommandInfo info, int width = 320, int height = 200)
: base(300, 50)
{
AddPage(0);
AddBackground(0, 0, width, height, 5054);
//AddImageTiled( 10, 10, width - 20, 20, 2624 );
//AddAlphaRegion( 10, 10, width - 20, 20 );
//AddHtmlLocalized( 10, 10, width - 20, 20, header, headerColor, false, false );
AddHtml(10, 10, width - 20, 20, Color(Center(info.Name), 0xFF0000));
//AddImageTiled( 10, 40, width - 20, height - 80, 2624 );
//AddAlphaRegion( 10, 40, width - 20, height - 80 );
StringBuilder sb = new StringBuilder();
sb.Append("Usage: ");
sb.Append(info.Usage.Replace("<", "(").Replace(">", ")"));
sb.Append("<BR>");
string[] aliases = info.Aliases;
if (aliases?.Length > 0)
{
sb.Append($"Alias{(aliases.Length == 1 ? "" : "es")}: ");
for (int i = 0; i < aliases.Length; ++i)
{
if (i != 0)
sb.Append(", ");
sb.Append(aliases[i]);
}
sb.Append("<BR>");
}
sb.Append("AccessLevel: ");
sb.Append(info.AccessLevel.ToString());
sb.Append("<BR>");
sb.Append("<BR>");
sb.Append(info.Description);
AddHtml(10, 40, width - 20, height - 80, sb.ToString(), false, true);
//AddImageTiled( 10, height - 30, width - 20, 20, 2624 );
//AddAlphaRegion( 10, height - 30, width - 20, 20 );
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
}
}
}

View file

@ -0,0 +1,141 @@
using System;
using System.IO;
using System.Text;
using Server.Accounting;
namespace Server.Commands
{
public class CommandLogging
{
private static char[] m_NotSafe = { '\\', '/', ':', '*', '?', '"', '<', '>', '|' };
public static bool Enabled{ get; set; } = true;
public static StreamWriter Output{ get; private set; }
public static void Initialize()
{
EventSink.Command += EventSink_Command;
if (!Directory.Exists("Logs"))
Directory.CreateDirectory("Logs");
string directory = "Logs/Commands";
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
try
{
Output = new StreamWriter(Path.Combine(directory, $"{DateTime.UtcNow.ToLongDateString()}.log"), true);
Output.AutoFlush = true;
Output.WriteLine("##############################");
Output.WriteLine("Log started on {0}", DateTime.UtcNow);
Output.WriteLine();
}
catch
{
// ignored
}
}
public static object Format(object o)
{
if (o is Mobile m)
{
if (m.Account == null)
return $"{m} (no account)";
return $"{m} ('{m.Account.Username}')";
}
if (o is Item item) return $"0x{item.Serial.Value:X} ({item.GetType().Name})";
return o;
}
public static void WriteLine(Mobile from, string format, params object[] args)
{
if (!Enabled)
return;
WriteLine(from, string.Format(format, args));
}
public static void WriteLine(Mobile from, string text)
{
if (!Enabled)
return;
try
{
Output.WriteLine("{0}: {1}: {2}", DateTime.UtcNow, from.NetState, text);
string path = Core.BaseDirectory;
string name = !(from.Account is Account acct) ? from.Name : acct.Username;
AppendPath(ref path, "Logs");
AppendPath(ref path, "Commands");
AppendPath(ref path, from.AccessLevel.ToString());
path = Path.Combine(path, $"{name}.log");
using (StreamWriter sw = new StreamWriter(path, true))
{
sw.WriteLine("{0}: {1}: {2}", DateTime.UtcNow, from.NetState, text);
}
}
catch
{
// ignored
}
}
public static void AppendPath(ref string path, string toAppend)
{
path = Path.Combine(path, toAppend);
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
}
public static string Safe(string ip)
{
if (ip == null)
return "null";
ip = ip.Trim();
if (ip.Length == 0)
return "empty";
bool isSafe = true;
for (int i = 0; isSafe && i < m_NotSafe.Length; ++i)
isSafe = ip.IndexOf(m_NotSafe[i]) == -1;
if (isSafe)
return ip;
StringBuilder sb = new StringBuilder(ip);
for (int i = 0; i < m_NotSafe.Length; ++i)
sb.Replace(m_NotSafe[i], '_');
return sb.ToString();
}
public static void EventSink_Command(CommandEventArgs e)
{
WriteLine(e.Mobile, "{0} {1} used command '{2} {3}'", e.Mobile.AccessLevel, Format(e.Mobile), e.Command,
e.ArgString);
}
public static void LogChangeProperty(Mobile from, object o, string name, string value)
{
WriteLine(from, "{0} {1} set property '{2}' of {3} to '{4}'", from.AccessLevel, Format(from), name, Format(o),
value);
}
}
}

View file

@ -0,0 +1,369 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Server.Diagnostics;
namespace Server.Commands
{
public class Profiling
{
public static void Initialize()
{
CommandSystem.Register("DumpTimers", AccessLevel.Administrator, DumpTimers_OnCommand);
CommandSystem.Register("CountObjects", AccessLevel.Administrator, CountObjects_OnCommand);
CommandSystem.Register("ProfileWorld", AccessLevel.Administrator, ProfileWorld_OnCommand);
CommandSystem.Register("TraceInternal", AccessLevel.Administrator, TraceInternal_OnCommand);
CommandSystem.Register("TraceExpanded", AccessLevel.Administrator, TraceExpanded_OnCommand);
CommandSystem.Register("WriteProfiles", AccessLevel.Administrator, WriteProfiles_OnCommand);
CommandSystem.Register("SetProfiles", AccessLevel.Administrator, SetProfiles_OnCommand);
}
[Usage("WriteProfiles")]
[Description("Generates a log files containing performance diagnostic information.")]
public static void WriteProfiles_OnCommand(CommandEventArgs e)
{
try
{
using (StreamWriter sw = new StreamWriter("profiles.log", true))
{
sw.WriteLine("# Dump on {0:f}", DateTime.UtcNow);
sw.WriteLine("# Core profiling for " + Core.ProfileTime);
sw.WriteLine("# Packet send");
BaseProfile.WriteAll(sw, PacketSendProfile.Profiles);
sw.WriteLine();
sw.WriteLine("# Packet receive");
BaseProfile.WriteAll(sw, PacketReceiveProfile.Profiles);
sw.WriteLine();
sw.WriteLine("# Timer");
BaseProfile.WriteAll(sw, TimerProfile.Profiles);
sw.WriteLine();
sw.WriteLine("# Gump response");
BaseProfile.WriteAll(sw, GumpProfile.Profiles);
sw.WriteLine();
sw.WriteLine("# Target response");
BaseProfile.WriteAll(sw, TargetProfile.Profiles);
sw.WriteLine();
}
}
catch
{
// ignored
}
}
[Usage("SetProfiles [true | false]")]
[Description("Enables, disables, or toggles the state of core packet and timer profiling.")]
public static void SetProfiles_OnCommand(CommandEventArgs e)
{
if (e.Length == 1)
Core.Profiling = e.GetBoolean(0);
else
Core.Profiling = !Core.Profiling;
e.Mobile.SendMessage("Profiling has been {0}.", Core.Profiling ? "enabled" : "disabled");
}
[Usage("DumpTimers")]
[Description("Generates a log file of all currently executing timers. Used for tracing timer leaks.")]
public static void DumpTimers_OnCommand(CommandEventArgs e)
{
try
{
using (StreamWriter sw = new StreamWriter("timerdump.log", true))
{
Timer.DumpInfo(sw);
}
}
catch
{
// ignored
}
}
[Usage("CountObjects")]
[Description("Generates a log file detailing all item and mobile types in the world.")]
public static void CountObjects_OnCommand(CommandEventArgs e)
{
using (StreamWriter op = new StreamWriter("objects.log"))
{
Dictionary<Type, int> table = new Dictionary<Type, int>();
foreach (Item item in World.Items.Values)
{
Type type = item.GetType();
table[type] = (table.TryGetValue(type, out int value) ? value : 0) + 1;
}
List<KeyValuePair<Type, int>> items = table.ToList();
table.Clear();
foreach (Mobile m in World.Mobiles.Values)
{
Type type = m.GetType();
table[type] = (table.TryGetValue(type, out int value) ? value : 0) + 1;
}
List<KeyValuePair<Type, int>> mobiles = table.ToList();
items.Sort(new CountSorter());
mobiles.Sort(new CountSorter());
op.WriteLine("# Object count table generated on {0}", DateTime.UtcNow);
op.WriteLine();
op.WriteLine();
op.WriteLine("# Items:");
items.ForEach(kvp =>
op.WriteLine("{0}\t{1:F2}%\t{2}", kvp.Value, 100.0 * kvp.Value / World.Items.Count, kvp.Key));
op.WriteLine();
op.WriteLine();
op.WriteLine("#Mobiles:");
mobiles.ForEach(kvp =>
op.WriteLine("{0}\t{1:F2}%\t{2}", kvp.Value, 100.0 * kvp.Value / World.Mobiles.Count, kvp.Key));
}
e.Mobile.SendMessage("Object table has been generated. See the file : <runuo root>/objects.log");
}
[Usage("TraceExpanded")]
[Description("Generates a log file describing all items using expanded memory.")]
public static void TraceExpanded_OnCommand(CommandEventArgs e)
{
Dictionary<Type, int[]> typeTable = new Dictionary<Type, int[]>();
foreach (Item item in World.Items.Values)
{
ExpandFlag flags = item.GetExpandFlags();
if ((flags & ~(ExpandFlag.TempFlag | ExpandFlag.SaveFlag)) == 0)
continue;
Type itemType = item.GetType();
do
{
if (!typeTable.TryGetValue(itemType, out int[] countTable))
typeTable[itemType] = countTable = new int[9];
if ((flags & ExpandFlag.Name) != 0)
++countTable[0];
if ((flags & ExpandFlag.Items) != 0)
++countTable[1];
if ((flags & ExpandFlag.Bounce) != 0)
++countTable[2];
if ((flags & ExpandFlag.Holder) != 0)
++countTable[3];
if ((flags & ExpandFlag.Blessed) != 0)
++countTable[4];
/*if ( ( flags & ExpandFlag.TempFlag ) != 0 )
++countTable[5];
if ( ( flags & ExpandFlag.SaveFlag ) != 0 )
++countTable[6];*/
if ((flags & ExpandFlag.Weight) != 0)
++countTable[7];
if ((flags & ExpandFlag.Spawner) != 0)
++countTable[8];
itemType = itemType.BaseType;
} while (itemType != typeof(object));
}
try
{
using (StreamWriter op = new StreamWriter("expandedItems.log", true))
{
string[] names =
{
"Name",
"Items",
"Bounce",
"Holder",
"Blessed",
"TempFlag",
"SaveFlag",
"Weight",
"Spawner"
};
List<KeyValuePair<Type, int[]>> list = typeTable.ToList();
list.Sort(new CountsSorter());
foreach (KeyValuePair<Type, int[]> kvp in list)
{
int[] countTable = kvp.Value;
op.WriteLine("# {0}", kvp.Key.FullName);
for (int i = 0; i < countTable.Length; ++i)
if (countTable[i] > 0)
op.WriteLine("{0}\t{1:N0}", names[i], countTable[i]);
op.WriteLine();
}
}
}
catch
{
// ignored
}
}
[Usage("TraceInternal")]
[Description("Generates a log file describing all items in the 'internal' map.")]
public static void TraceInternal_OnCommand(CommandEventArgs e)
{
int totalCount = 0;
Dictionary<Type, int[]> table = new Dictionary<Type, int[]>();
foreach (Item item in World.Items.Values)
{
if (item.Parent != null || item.Map != Map.Internal)
continue;
++totalCount;
Type type = item.GetType();
if (table.TryGetValue(type, out int[] parms))
{
parms[0]++;
parms[1] += item.Amount;
} else
table[type] = new[] { 1, item.Amount };
}
using (StreamWriter op = new StreamWriter("internal.log"))
{
op.WriteLine("# {0} items found", totalCount);
op.WriteLine("# {0} different types", table.Count);
op.WriteLine();
op.WriteLine();
op.WriteLine("Type\t\tCount\t\tAmount\t\tAvg. Amount");
foreach (KeyValuePair<Type, int[]> de in table)
{
int[] parms = de.Value;
op.WriteLine("{0}\t\t{1}\t\t{2}\t\t{3:F2}", de.Key.Name, parms[0], parms[1], (double)parms[1] / parms[0]);
}
}
}
[Usage("ProfileWorld")]
[Description("Prints the amount of data serialized for every object type in your world file.")]
public static void ProfileWorld_OnCommand(CommandEventArgs e)
{
ProfileWorld("items", "worldprofile_items.log");
ProfileWorld("mobiles", "worldprofile_mobiles.log");
}
public static void ProfileWorld(string type, string opFile)
{
try
{
List<Type> types = new List<Type>();
using (BinaryReader bin = new BinaryReader(new FileStream(string.Format("Saves/{0}/{0}.tdb", type),
FileMode.Open, FileAccess.Read, FileShare.Read)))
{
int count = bin.ReadInt32();
for (int i = 0; i < count; ++i)
types.Add(ScriptCompiler.FindTypeByFullName(bin.ReadString()));
}
long total = 0;
Dictionary<Type, int> table = new Dictionary<Type, int>();
using (BinaryReader bin = new BinaryReader(new FileStream(string.Format("Saves/{0}/{0}.idx", type),
FileMode.Open, FileAccess.Read, FileShare.Read)))
{
int count = bin.ReadInt32();
for (int i = 0; i < count; ++i)
{
int typeID = bin.ReadInt32();
int serial = bin.ReadInt32();
long pos = bin.ReadInt64();
int length = bin.ReadInt32();
Type objType = types[typeID];
while (objType != null && objType != typeof(object))
{
table[objType] = length + (table.TryGetValue(objType, out int value) ? value : 0);
objType = objType.BaseType;
total += length;
}
}
}
List<KeyValuePair<Type, int>> list = table.ToList();
list.Sort(new CountSorter());
using (StreamWriter op = new StreamWriter(opFile))
{
op.WriteLine("# Profile of world {0}", type);
op.WriteLine("# Generated on {0}", DateTime.UtcNow);
op.WriteLine();
op.WriteLine();
list.ForEach(kvp =>
op.WriteLine("{0}\t{1:F2}%\t{2}", kvp.Value, 100.0 * kvp.Value / total, kvp.Key));
}
}
catch
{
// ignored
}
}
private class CountSorter : IComparer<KeyValuePair<Type, int>>
{
public int Compare(KeyValuePair<Type, int> x, KeyValuePair<Type, int> y)
{
int aCount = x.Value;
int bCount = y.Value;
int v = -aCount.CompareTo(bCount);
return v != 0 ? v : x.Key.FullName.CompareTo(y.Key.FullName);
}
}
private class CountsSorter : IComparer<KeyValuePair<Type, int[]>>
{
public int Compare(KeyValuePair<Type, int[]> x, KeyValuePair<Type, int[]> y)
{
int aCount = x.Value.Aggregate(0, (t, val) => t + val);
int bCount = y.Value.Aggregate(0, (t, val) => t + val);
int v = -aCount.CompareTo(bCount);
return v != 0 ? v : x.Key.FullName.CompareTo(y.Key.FullName);
}
}
}
}

View file

@ -0,0 +1,786 @@
using System;
using System.Reflection;
using Server.Commands;
using Server.Commands.Generic;
using Server.Gumps;
using Server.Targeting;
using CPA = Server.CommandPropertyAttribute;
namespace Server.Commands
{
[Flags]
public enum PropertyAccess
{
Read = 0x01,
Write = 0x02,
ReadWrite = Read | Write
}
public static class Properties
{
private static Type typeofCPA = typeof(CPA);
private static Type typeofSerial = typeof(Serial);
private static Type typeofType = typeof(Type);
private static Type typeofChar = typeof(char);
private static Type typeofString = typeof(string);
private static Type typeofText = typeof(TextDefinition);
private static Type typeofTimeSpan = typeof(TimeSpan);
private static Type typeofParsable = typeof(ParsableAttribute);
private static Type[] m_ParseTypes = { typeof(string) };
private static object[] m_ParseParams = new object[1];
private static Type[] m_NumericTypes =
{
typeof(byte), typeof(sbyte),
typeof(short), typeof(ushort),
typeof(int), typeof(uint),
typeof(long), typeof(ulong)
};
public static void Initialize()
{
CommandSystem.Register("Props", AccessLevel.Counselor, Props_OnCommand);
}
[Usage("Props [serial]")]
[Description("Opens a menu where you can view and edit all properties of a targeted (or specified) object.")]
private static void Props_OnCommand(CommandEventArgs e)
{
if (e.Length == 1)
{
IEntity ent = World.FindEntity(e.GetUInt32(0));
if (ent == null)
e.Mobile.SendMessage("No object with that serial was found.");
else if (!BaseCommand.IsAccessible(e.Mobile, ent))
e.Mobile.SendLocalizedMessage(500447); // That is not accessible.
else
e.Mobile.SendGump(new PropertiesGump(e.Mobile, ent));
}
else
{
e.Mobile.Target = new PropsTarget();
}
}
private static bool CIEqual(string l, string r)
{
return Insensitive.Equals(l, r);
}
public static CPA GetCPA(PropertyInfo p)
{
object[] attrs = p.GetCustomAttributes(typeofCPA, false);
if (attrs.Length == 0)
return null;
return attrs[0] as CPA;
}
public static PropertyInfo[] GetPropertyInfoChain(Mobile from, Type type, string propertyString,
PropertyAccess endAccess, ref string failReason)
{
string[] split = propertyString.Split('.');
if (split.Length == 0)
return null;
PropertyInfo[] info = new PropertyInfo[split.Length];
for (int i = 0; i < info.Length; ++i)
{
string propertyName = split[i];
if (CIEqual(propertyName, "current"))
continue;
PropertyInfo[] props = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);
bool isFinal = i == info.Length - 1;
PropertyAccess access = endAccess;
if (!isFinal)
access |= PropertyAccess.Read;
for (int j = 0; j < props.Length; ++j)
{
PropertyInfo p = props[j];
if (CIEqual(p.Name, propertyName))
{
CPA attr = GetCPA(p);
if (attr == null)
{
failReason = $"Property '{propertyName}' not found.";
return null;
}
if ((access & PropertyAccess.Read) != 0 && from.AccessLevel < attr.ReadLevel)
{
failReason =
$"You must be at least {Mobile.GetAccessLevelName(attr.ReadLevel)} to get the property '{propertyName}'.";
return null;
}
if ((access & PropertyAccess.Write) != 0 && from.AccessLevel < attr.WriteLevel)
{
failReason =
$"You must be at least {Mobile.GetAccessLevelName(attr.WriteLevel)} to set the property '{propertyName}'.";
return null;
}
if ((access & PropertyAccess.Read) != 0 && !p.CanRead)
{
failReason = $"Property '{propertyName}' is write only.";
return null;
}
if ((access & PropertyAccess.Write) != 0 && (!p.CanWrite || attr.ReadOnly) && isFinal)
{
failReason = $"Property '{propertyName}' is read only.";
return null;
}
info[i] = p;
type = p.PropertyType;
break;
}
}
if (info[i] == null)
{
failReason = $"Property '{propertyName}' not found.";
return null;
}
}
return info;
}
public static PropertyInfo GetPropertyInfo(Mobile from, ref object obj, string propertyName, PropertyAccess access,
ref string failReason)
{
PropertyInfo[] chain = GetPropertyInfoChain(from, obj.GetType(), propertyName, access, ref failReason);
return chain == null ? null : GetPropertyInfo(ref obj, chain, ref failReason);
}
public static PropertyInfo GetPropertyInfo(ref object obj, PropertyInfo[] chain, ref string failReason)
{
if (chain == null || chain.Length == 0)
{
failReason = "Property chain is empty.";
return null;
}
for (int i = 0; i < chain.Length - 1; ++i)
{
if (chain[i] == null)
continue;
obj = chain[i].GetValue(obj, null);
if (obj == null)
{
failReason = $"Property '{chain[i]}' is null.";
return null;
}
}
return chain[chain.Length - 1];
}
public static string GetValue(Mobile from, object o, string name)
{
string failReason = "";
PropertyInfo[] chain = GetPropertyInfoChain(from, o.GetType(), name, PropertyAccess.Read, ref failReason);
if (chain == null || chain.Length == 0)
return failReason;
PropertyInfo p = GetPropertyInfo(ref o, chain, ref failReason);
return p == null ? failReason : InternalGetValue(o, p, chain);
}
public static string IncreaseValue(Mobile from, object o, string[] args)
{
// Type type = o.GetType();
object[] realObjs = new object[args.Length / 2];
PropertyInfo[] realProps = new PropertyInfo[args.Length / 2];
int[] realValues = new int[args.Length / 2];
bool positive = false;
bool negative = false;
for (int i = 0; i < realProps.Length; ++i)
{
string name = args[i * 2];
try
{
string valueString = args[1 + i * 2];
if (valueString.StartsWith("0x"))
realValues[i] = Convert.ToInt32(valueString.Substring(2), 16);
else
realValues[i] = Convert.ToInt32(valueString);
}
catch
{
return "Offset value could not be parsed.";
}
if (realValues[i] > 0)
positive = true;
else if (realValues[i] < 0)
negative = true;
else
return "Zero is not a valid value to offset.";
string failReason = null;
realObjs[i] = o;
realProps[i] = GetPropertyInfo(from, ref realObjs[i], name, PropertyAccess.ReadWrite, ref failReason);
if (failReason != null)
return failReason;
if (realProps[i] == null)
return "Property not found.";
}
for (int i = 0; i < realProps.Length; ++i)
{
object obj = realProps[i].GetValue(realObjs[i], null);
if (!(obj is IConvertible))
return "Property is not IConvertable.";
try
{
long v = (long)Convert.ChangeType(obj, TypeCode.Int64);
v += realValues[i];
realProps[i].SetValue(realObjs[i], Convert.ChangeType(v, realProps[i].PropertyType), null);
}
catch
{
return "Value could not be converted";
}
}
if (realProps.Length == 1)
{
if (positive)
return "The property has been increased.";
return "The property has been decreased.";
}
if (positive && negative)
return "The properties have been changed.";
if (positive)
return "The properties have been increased.";
return "The properties have been decreased.";
}
private static string InternalGetValue(object o, PropertyInfo p, PropertyInfo[] chain = null)
{
Type type = p.PropertyType;
object value = p.GetValue(o, null);
string toString;
if (value == null)
toString = "null";
else if (IsNumeric(type))
toString = $"{value} (0x{value:X})";
else if (IsChar(type))
toString = $"'{value}' ({(int)value} [0x{(int)value:X}])";
else if (IsString(type))
toString = (string)value == "null" ? @"@""null""" : $"\"{value}\"";
else if (IsText(type))
toString = ((TextDefinition)value).Format(false);
else
toString = value.ToString();
if (chain == null)
return $"{p.Name} = {toString}";
string[] concat = new string[chain.Length * 2 + 1];
for (int i = 0; i < chain.Length; ++i)
{
concat[i * 2 + 0] = chain[i].Name;
concat[i * 2 + 1] = i < chain.Length - 1 ? "." : " = ";
}
concat[concat.Length - 1] = toString;
return string.Concat(concat);
}
public static string SetValue(Mobile from, object o, string name, string value)
{
object logObject = o;
string failReason = "";
PropertyInfo p = GetPropertyInfo(from, ref o, name, PropertyAccess.Write, ref failReason);
return p == null ? failReason : InternalSetValue(from, logObject, o, p, name, value, true);
}
private static bool IsSerial(Type t)
{
return t == typeofSerial;
}
private static bool IsType(Type t)
{
return t == typeofType;
}
private static bool IsChar(Type t)
{
return t == typeofChar;
}
private static bool IsString(Type t)
{
return t == typeofString;
}
private static bool IsText(Type t)
{
return t == typeofText;
}
private static bool IsEnum(Type t)
{
return t.IsEnum;
}
private static bool IsParsable(Type t)
{
return t == typeofTimeSpan || t.IsDefined(typeofParsable, false);
}
private static object Parse(object o, Type t, string value)
{
MethodInfo method = t.GetMethod("Parse", m_ParseTypes);
m_ParseParams[0] = value;
return method?.Invoke(o, m_ParseParams);
}
private static bool IsNumeric(Type t)
{
return Array.IndexOf(m_NumericTypes, t) >= 0;
}
public static string ConstructFromString(Type type, object obj, string value, ref object constructed)
{
object toSet;
bool isSerial = IsSerial(type);
if (isSerial) // mutate into int32
type = m_NumericTypes[4];
if (value == "(-null-)" && !type.IsValueType)
value = null;
if (IsEnum(type))
try
{
toSet = Enum.Parse(type, value, true);
}
catch
{
return "That is not a valid enumeration member.";
}
else if (IsType(type))
try
{
toSet = ScriptCompiler.FindTypeByName(value);
if (toSet == null)
return "No type with that name was found.";
}
catch
{
return "No type with that name was found.";
}
else if (IsParsable(type))
try
{
toSet = Parse(obj, type, value);
}
catch
{
return "That is not properly formatted.";
}
else if (value == null)
toSet = null;
else if (value.StartsWith("0x") && IsNumeric(type))
try
{
toSet = Convert.ChangeType(Convert.ToUInt64(value.Substring(2), 16), type);
}
catch
{
return "That is not properly formatted.";
}
else
try
{
toSet = Convert.ChangeType(value, type);
}
catch
{
return "That is not properly formatted.";
}
if (isSerial) // mutate back
toSet = (Serial)(toSet ?? Serial.MinusOne);
constructed = toSet;
return null;
}
public static string SetDirect(Mobile from, object logObject, object obj, PropertyInfo prop, string givenName,
object toSet, bool shouldLog)
{
try
{
if (toSet is AccessLevel newLevel)
{
AccessLevel reqLevel = AccessLevel.Administrator;
if (newLevel == AccessLevel.Administrator)
reqLevel = AccessLevel.Developer;
else if (newLevel >= AccessLevel.Developer)
reqLevel = AccessLevel.Owner;
if (from.AccessLevel < reqLevel)
return "You do not have access to that level.";
}
if (shouldLog)
CommandLogging.LogChangeProperty(from, logObject, givenName,
toSet == null ? "(-null-)" : toSet.ToString());
prop.SetValue(obj, toSet, null);
return "Property has been set.";
}
catch
{
return "An exception was caught, the property may not be set.";
}
}
public static string SetDirect(object obj, PropertyInfo prop, object toSet)
{
try
{
if (toSet is AccessLevel) return "You do not have access to that level.";
prop.SetValue(obj, toSet, null);
return "Property has been set.";
}
catch
{
return "An exception was caught, the property may not be set.";
}
}
public static string InternalSetValue(Mobile from, object logobj, object o, PropertyInfo p, string pname,
string value, bool shouldLog)
{
object toSet = null;
string result = ConstructFromString(p.PropertyType, o, value, ref toSet);
return result ?? SetDirect(from, logobj, o, p, pname, toSet, shouldLog);
}
public static string InternalSetValue(object o, PropertyInfo p, string value)
{
object toSet = null;
string result = ConstructFromString(p.PropertyType, o, value, ref toSet);
return result ?? SetDirect(o, p, toSet);
}
private class PropsTarget : Target
{
public PropsTarget() : base(-1, true, TargetFlags.None)
{
}
protected override void OnTarget(Mobile from, object o)
{
if (!BaseCommand.IsAccessible(from, o))
from.SendLocalizedMessage(500447); // That is not accessible.
else
from.SendGump(new PropertiesGump(from, o));
}
}
}
}
namespace Server
{
public abstract class PropertyException : ApplicationException
{
protected Property m_Property;
public PropertyException(Property property, string message)
: base(message)
{
m_Property = property;
}
public Property Property => m_Property;
}
public abstract class BindingException : PropertyException
{
public BindingException(Property property, string message)
: base(property, message)
{
}
}
public sealed class NotYetBoundException : BindingException
{
public NotYetBoundException(Property property)
: base(property, "Property has not yet been bound.")
{
}
}
public sealed class AlreadyBoundException : BindingException
{
public AlreadyBoundException(Property property)
: base(property, "Property has already been bound.")
{
}
}
public sealed class UnknownPropertyException : BindingException
{
public UnknownPropertyException(Property property, string current)
: base(property, $"Property '{current}' not found.")
{
}
}
public sealed class ReadOnlyException : BindingException
{
public ReadOnlyException(Property property)
: base(property, "Property is read-only.")
{
}
}
public sealed class WriteOnlyException : BindingException
{
public WriteOnlyException(Property property)
: base(property, "Property is write-only.")
{
}
}
public abstract class AccessException : PropertyException
{
public AccessException(Property property, string message)
: base(property, message)
{
}
}
public sealed class InternalAccessException : AccessException
{
public InternalAccessException(Property property)
: base(property, "Property is internal.")
{
}
}
public abstract class ClearanceException : AccessException
{
public ClearanceException(Property property, AccessLevel playerAccess, AccessLevel neededAccess, string accessType)
: base(property,
$"You must be at least {Mobile.GetAccessLevelName(neededAccess)} to {accessType} this property.")
{
}
public AccessLevel PlayerAccess{ get; set; }
public AccessLevel NeededAccess{ get; set; }
}
public sealed class ReadAccessException : ClearanceException
{
public ReadAccessException(Property property, AccessLevel playerAccess, AccessLevel neededAccess)
: base(property, playerAccess, neededAccess, "read")
{
}
}
public sealed class WriteAccessException : ClearanceException
{
public WriteAccessException(Property property, AccessLevel playerAccess, AccessLevel neededAccess)
: base(property, playerAccess, neededAccess, "write")
{
}
}
public sealed class Property
{
private PropertyInfo[] m_Chain;
public Property(string binding)
{
Binding = binding;
}
public Property(PropertyInfo[] chain)
{
m_Chain = chain;
}
public string Binding{ get; }
public bool IsBound => m_Chain != null;
public PropertyAccess Access{ get; private set; }
public PropertyInfo[] Chain
{
get
{
if (!IsBound)
throw new NotYetBoundException(this);
return m_Chain;
}
}
public Type Type
{
get
{
if (!IsBound)
throw new NotYetBoundException(this);
return m_Chain[m_Chain.Length - 1].PropertyType;
}
}
public bool CheckAccess(Mobile from)
{
if (!IsBound)
throw new NotYetBoundException(this);
for (int i = 0; i < m_Chain.Length; ++i)
{
PropertyInfo prop = m_Chain[i];
bool isFinal = i == m_Chain.Length - 1;
PropertyAccess access = Access;
if (!isFinal)
access |= PropertyAccess.Read;
CPA security = Properties.GetCPA(prop);
if (security == null)
throw new InternalAccessException(this);
if ((access & PropertyAccess.Read) != 0 && from.AccessLevel < security.ReadLevel)
throw new ReadAccessException(this, from.AccessLevel, security.ReadLevel);
if ((access & PropertyAccess.Write) != 0 && (from.AccessLevel < security.WriteLevel || security.ReadOnly))
throw new WriteAccessException(this, from.AccessLevel, security.ReadLevel);
}
return true;
}
public void BindTo(Type objectType, PropertyAccess desiredAccess)
{
if (IsBound)
throw new AlreadyBoundException(this);
string[] split = Binding.Split('.');
PropertyInfo[] chain = new PropertyInfo[split.Length];
for (int i = 0; i < split.Length; ++i)
{
bool isFinal = i == chain.Length - 1;
chain[i] = objectType.GetProperty(split[i],
BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
if (chain[i] == null)
throw new UnknownPropertyException(this, split[i]);
objectType = chain[i].PropertyType;
PropertyAccess access = desiredAccess;
if (!isFinal)
access |= PropertyAccess.Read;
if ((access & PropertyAccess.Read) != 0 && !chain[i].CanRead)
throw new WriteOnlyException(this);
if ((access & PropertyAccess.Write) != 0 && !chain[i].CanWrite)
throw new ReadOnlyException(this);
}
Access = desiredAccess;
m_Chain = chain;
}
public override string ToString()
{
if (!IsBound)
return Binding;
string[] toJoin = new string[m_Chain.Length];
for (int i = 0; i < toJoin.Length; ++i)
toJoin[i] = m_Chain[i].Name;
return string.Join(".", toJoin);
}
public static Property Parse(Type type, string binding, PropertyAccess access)
{
Property prop = new Property(binding);
prop.BindTo(type, access);
return prop;
}
}
}

View file

@ -0,0 +1,19 @@
using System;
namespace Server.Commands
{
public class ShardTime
{
public static void Initialize()
{
CommandSystem.Register("Time", AccessLevel.Player, Time_OnCommand);
}
[Usage("Time")]
[Description("Returns the server's local time.")]
private static void Time_OnCommand(CommandEventArgs e)
{
e.Mobile.SendMessage(DateTime.UtcNow.ToString());
}
}
}

View file

@ -0,0 +1,148 @@
using System.Collections.Generic;
using System.IO;
using Server.Items;
namespace Server.Commands
{
public class SignParser
{
private static Queue<Item> m_ToDelete = new Queue<Item>();
public static void Initialize()
{
CommandSystem.Register("SignGen", AccessLevel.Administrator, SignGen_OnCommand);
}
[Usage("SignGen")]
[Description("Generates world/shop signs on all facets.")]
public static void SignGen_OnCommand(CommandEventArgs c)
{
Parse(c.Mobile);
}
public static void Parse(Mobile from)
{
string cfg = Path.Combine(Core.BaseDirectory, "Data/signs.cfg");
if (File.Exists(cfg))
{
List<SignEntry> list = new List<SignEntry>();
from.SendMessage("Generating signs, please wait.");
using (StreamReader ip = new StreamReader(cfg))
{
string line;
while ((line = ip.ReadLine()) != null)
{
string[] split = line.Split(' ');
SignEntry e = new SignEntry(
line.Substring(split[0].Length + 1 + split[1].Length + 1 + split[2].Length + 1 +
split[3].Length + 1 + split[4].Length + 1),
new Point3D(Utility.ToInt32(split[2]), Utility.ToInt32(split[3]), Utility.ToInt32(split[4])),
Utility.ToInt32(split[1]), Utility.ToInt32(split[0]));
list.Add(e);
}
}
Map[] brit = { Map.Felucca, Map.Trammel };
Map[] fel = { Map.Felucca };
Map[] tram = { Map.Trammel };
Map[] ilsh = { Map.Ilshenar };
Map[] malas = { Map.Malas };
Map[] tokuno = { Map.Tokuno };
for (int i = 0; i < list.Count; ++i)
{
SignEntry e = list[i];
Map[] maps = null;
switch (e.m_Map)
{
case 0:
maps = brit;
break; // Trammel and Felucca
case 1:
maps = fel;
break; // Felucca
case 2:
maps = tram;
break; // Trammel
case 3:
maps = ilsh;
break; // Ilshenar
case 4:
maps = malas;
break; // Malas
case 5:
maps = tokuno;
break; // Tokuno Islands
}
for (int j = 0; maps?.Length >= j; ++j)
Add_Static(e.m_ItemID, e.m_Location, maps[j], e.m_Text);
}
from.SendMessage("Sign generating complete.");
}
else
{
from.SendMessage("{0} not found!", cfg);
}
}
public static void Add_Static(int itemID, Point3D location, Map map, string name)
{
IPooledEnumerable<Item> eable = map.GetItemsInRange(location, 0);
foreach (Item item in eable)
if (item is Sign && item.Z == location.Z && item.ItemID == itemID)
m_ToDelete.Enqueue(item);
eable.Free();
while (m_ToDelete.Count > 0)
m_ToDelete.Dequeue().Delete();
Item sign;
if (name.StartsWith("#"))
{
sign = new LocalizedSign(itemID, Utility.ToInt32(name.Substring(1)));
}
else
{
sign = new Sign(itemID);
sign.Name = name;
}
if (map == Map.Malas)
{
if (location.X >= 965 && location.Y >= 502 && location.X <= 1012 && location.Y <= 537)
sign.Hue = 0x47E;
else if (location.X >= 1960 && location.Y >= 1278 && location.X < 2106 && location.Y < 1413)
sign.Hue = 0x44E;
}
sign.MoveToWorld(location, map);
}
private class SignEntry
{
public int m_ItemID;
public Point3D m_Location;
public int m_Map;
public string m_Text;
public SignEntry(string text, Point3D pt, int itemID, int mapLoc)
{
m_Text = text;
m_Location = pt;
m_ItemID = itemID;
m_Map = mapLoc;
}
}
}
}

View file

@ -0,0 +1,129 @@
using System;
using Server.Targeting;
namespace Server.Commands
{
public class SkillsCommand
{
public static void Initialize()
{
CommandSystem.Register("SetSkill", AccessLevel.GameMaster, SetSkill_OnCommand);
CommandSystem.Register("GetSkill", AccessLevel.GameMaster, GetSkill_OnCommand);
CommandSystem.Register("SetAllSkills", AccessLevel.GameMaster, SetAllSkills_OnCommand);
}
[Usage("SetSkill <name> <value>")]
[Description("Sets a skill value by name of a targeted mobile.")]
public static void SetSkill_OnCommand(CommandEventArgs arg)
{
if (arg.Length != 2)
{
arg.Mobile.SendMessage("SetSkill <skill name> <value>");
}
else
{
if (Enum.TryParse(arg.GetString(0), true, out SkillName skill))
arg.Mobile.Target = new SkillTarget(skill, arg.GetDouble(1));
else
arg.Mobile.SendLocalizedMessage(1005631); // You have specified an invalid skill to set.
}
}
[Usage("SetAllSkills <name> <value>")]
[Description("Sets all skill values of a targeted mobile.")]
public static void SetAllSkills_OnCommand(CommandEventArgs arg)
{
if (arg.Length != 1)
arg.Mobile.SendMessage("SetAllSkills <value>");
else
arg.Mobile.Target = new AllSkillsTarget(arg.GetDouble(0));
}
[Usage("GetSkill <name>")]
[Description("Gets a skill value by name of a targeted mobile.")]
public static void GetSkill_OnCommand(CommandEventArgs arg)
{
if (arg.Length != 1)
{
arg.Mobile.SendMessage("GetSkill <skill name>");
}
else
{
if (Enum.TryParse(arg.GetString(0), true, out SkillName skill))
arg.Mobile.Target = new SkillTarget(skill);
else
arg.Mobile.SendMessage("You have specified an invalid skill to get.");
}
}
public class AllSkillsTarget : Target
{
private double m_Value;
public AllSkillsTarget(double value) : base(-1, false, TargetFlags.None)
{
m_Value = value;
}
protected override void OnTarget(Mobile from, object targeted)
{
if (targeted is Mobile targ)
{
Server.Skills skills = targ.Skills;
for (int i = 0; i < skills.Length; ++i)
skills[i].Base = m_Value;
CommandLogging.LogChangeProperty(from, targ, "EverySkill.Base", m_Value.ToString());
}
else
{
from.SendMessage("That does not have skills!");
}
}
}
public class SkillTarget : Target
{
private bool m_Set;
private SkillName m_Skill;
private double m_Value;
public SkillTarget(SkillName skill, double value) : base(-1, false, TargetFlags.None)
{
m_Set = true;
m_Skill = skill;
m_Value = value;
}
public SkillTarget(SkillName skill) : base(-1, false, TargetFlags.None)
{
m_Set = false;
m_Skill = skill;
}
protected override void OnTarget(Mobile from, object targeted)
{
if (targeted is Mobile targ)
{
Skill skill = targ.Skills[m_Skill];
if (skill == null)
return;
if (m_Set)
{
skill.Base = m_Value;
CommandLogging.LogChangeProperty(from, targ, $"{m_Skill}.Base", m_Value.ToString());
}
from.SendMessage("{0} : {1} (Base: {2})", m_Skill, skill.Value, skill.Base);
}
else
{
from.SendMessage("That does not have skills!");
}
}
}
}
}

View file

@ -0,0 +1,38 @@
using Server.Gumps;
using Server.Targeting;
namespace Server.Commands
{
public class Skills
{
public static void Initialize()
{
Register();
}
public static void Register()
{
CommandSystem.Register("Skills", AccessLevel.Counselor, Skills_OnCommand);
}
[Usage("Skills")]
[Description("Opens a menu where you can view or edit skills of a targeted mobile.")]
private static void Skills_OnCommand(CommandEventArgs e)
{
e.Mobile.Target = new SkillsTarget();
}
private class SkillsTarget : Target
{
public SkillsTarget() : base(-1, true, TargetFlags.None)
{
}
protected override void OnTarget(Mobile from, object o)
{
if (o is Mobile mobile)
from.SendGump(new SkillsGump(from, mobile));
}
}
}
}

View file

@ -0,0 +1,586 @@
using System.Collections.Generic;
using System.IO;
using Server.Commands;
using Server.Gumps;
using Server.Items;
namespace Server
{
public class Statics
{
private const string BaseFreezeWarning = "{0} " +
"Those items <u>will be removed from the world</u> and placed into the server data files. " +
"Other players <u>will not see the changes</u> unless you distribute your data files to them.<br><br>" +
"This operation may not complete unless the server and client are using different data files. " +
"If you receive a message stating 'output data files could not be opened,' then you are probably sharing data files. " +
"Create a new directory for the world data files (statics*.mul and staidx*.mul) and add that to Scritps/Misc/DataPath.cs.<br><br>" +
"The change will be in effect immediately on the server, however, you must restart your client and update it's data files for the changes to become visible. " +
"It is strongly recommended that you make backup of the data files mentioned above. " +
"Do you wish to proceed?";
private const string BaseUnfreezeWarning = "{0} " +
"Those items <u>will be removed from the static files</u> and exchanged with unmovable dynamic items. " +
"Other players <u>will not see the changes</u> unless you distribute your data files to them.<br><br>" +
"This operation may not complete unless the server and client are using different data files. " +
"If you receive a message stating 'output data files could not be opened,' then you are probably sharing data files. " +
"Create a new directory for the world data files (statics*.mul and staidx*.mul) and add that to Scritps/Misc/DataPath.cs.<br><br>" +
"The change will be in effect immediately on the server, however, you must restart your client and update it's data files for the changes to become visible. " +
"It is strongly recommended that you make backup of the data files mentioned above. " +
"Do you wish to proceed?";
private static Point3D NullP3D = new Point3D(int.MinValue, int.MinValue, int.MinValue);
private static byte[] m_Buffer;
private static StaticTile[] m_TileBuffer = new StaticTile[128];
public static void Initialize()
{
CommandSystem.Register("Freeze", AccessLevel.Administrator, Freeze_OnCommand);
CommandSystem.Register("FreezeMap", AccessLevel.Administrator, FreezeMap_OnCommand);
CommandSystem.Register("FreezeWorld", AccessLevel.Administrator, FreezeWorld_OnCommand);
CommandSystem.Register("Unfreeze", AccessLevel.Administrator, Unfreeze_OnCommand);
CommandSystem.Register("UnfreezeMap", AccessLevel.Administrator, UnfreezeMap_OnCommand);
CommandSystem.Register("UnfreezeWorld", AccessLevel.Administrator, UnfreezeWorld_OnCommand);
}
public delegate void FreezeCallback( Mobile from, bool okay, StateInfo si );
[Usage("Freeze")]
[Description("Makes a targeted area of dynamic items static.")]
public static void Freeze_OnCommand(CommandEventArgs e)
{
Mobile from = e.Mobile;
BoundingBoxPicker.Begin(from, (map, start, end) => FreezeBox_Callback(from, map, start, end));
}
[Usage("FreezeMap")]
[Description("Makes every dynamic item in your map static.")]
public static void FreezeMap_OnCommand(CommandEventArgs e)
{
Mobile from = e.Mobile;
Map map = from.Map;
if (map != null && map != Map.Internal)
SendWarning(from, "You are about to freeze <u>all items in {0}</u>.", BaseFreezeWarning, map, NullP3D,
NullP3D, FreezeWarning_Callback);
}
[Usage("FreezeWorld")]
[Description("Makes every dynamic item on all maps static.")]
public static void FreezeWorld_OnCommand(CommandEventArgs e)
{
SendWarning(e.Mobile, "You are about to freeze <u>every item on every map</u>.", BaseFreezeWarning, null,
NullP3D, NullP3D, FreezeWarning_Callback);
}
public static void SendWarning(Mobile m, string header, string baseWarning, Map map, Point3D start, Point3D end,
FreezeCallback callback)
{
m.SendGump(new WarningGump(1060635, 30720, string.Format(baseWarning, string.Format(header, map)), 0xFFC000, 420,
400, okay => callback(m, okay, new StateInfo(map, start, end))));
}
private static void FreezeBox_Callback(Mobile from, Map map, Point3D start, Point3D end)
{
SendWarning(from, "You are about to freeze a section of items.", BaseFreezeWarning, map, start, end,
FreezeWarning_Callback);
}
private static void FreezeWarning_Callback(Mobile from, bool okay, StateInfo si)
{
if (!okay)
return;
Freeze(from, si.m_Map, si.m_Start, si.m_End);
}
public static void Freeze(Mobile from, Map targetMap, Point3D start3d, Point3D end3d)
{
Dictionary<Map, Dictionary<Point2D, DeltaState>> mapTable = new Dictionary<Map, Dictionary<Point2D, DeltaState>>();
if (start3d == NullP3D && end3d == NullP3D)
{
if (targetMap == null)
CommandLogging.WriteLine(from, "{0} {1} invoking freeze for every item in every map", from.AccessLevel,
CommandLogging.Format(from));
else
CommandLogging.WriteLine(from, "{0} {1} invoking freeze for every item in {0}", from.AccessLevel,
CommandLogging.Format(from), targetMap);
foreach (Item item in World.Items.Values)
{
if (targetMap != null && item.Map != targetMap)
continue;
if (item.Parent != null)
continue;
if (item is Static || item is BaseFloor || item is BaseWall)
{
Map itemMap = item.Map;
if (itemMap == null || itemMap == Map.Internal)
continue;
if (!mapTable.TryGetValue(itemMap, out Dictionary<Point2D, DeltaState> table))
mapTable[itemMap] = table = new Dictionary<Point2D, DeltaState>();
Point2D p = new Point2D(item.X >> 3, item.Y >> 3);
if (!table.TryGetValue(p, out DeltaState state))
table[p] = state = new DeltaState(p);
state.m_List.Add(item);
}
}
}
else if (targetMap != null)
{
Point2D start = targetMap.Bound(new Point2D(start3d)), end = targetMap.Bound(new Point2D(end3d));
CommandLogging.WriteLine(from, "{0} {1} invoking freeze from {2} to {3} in {4}", from.AccessLevel,
CommandLogging.Format(from), start, end, targetMap);
IPooledEnumerable<Item> eable =
targetMap.GetItemsInBounds(new Rectangle2D(start.X, start.Y, end.X - start.X + 1, end.Y - start.Y + 1));
foreach (Item item in eable)
if (item is Static || item is BaseFloor || item is BaseWall)
{
Map itemMap = item.Map;
if (itemMap == null || itemMap == Map.Internal)
continue;
if (!mapTable.TryGetValue(itemMap, out Dictionary<Point2D, DeltaState> table))
mapTable[itemMap] = table = new Dictionary<Point2D, DeltaState>();
Point2D p = new Point2D(item.X >> 3, item.Y >> 3);
if (!table.TryGetValue(p, out DeltaState state))
table[p] = state = new DeltaState(p);
state.m_List.Add(item);
}
eable.Free();
}
if (mapTable.Count == 0)
{
from.SendGump(new NoticeGump(1060637, 30720,
"No freezable items were found. Only the following item types are frozen:<br> - Static<br> - BaseFloor<br> - BaseWall",
0xFFC000, 320, 240));
return;
}
bool badDataFile = false;
int totalFrozen = 0;
foreach (KeyValuePair<Map, Dictionary<Point2D, DeltaState>> de in mapTable)
{
Map map = de.Key;
Dictionary<Point2D, DeltaState> table = de.Value;
TileMatrix matrix = map.Tiles;
using (FileStream idxStream = OpenWrite(matrix.IndexStream))
{
using (FileStream mulStream = OpenWrite(matrix.DataStream))
{
if (idxStream == null || mulStream == null)
{
badDataFile = true;
continue;
}
BinaryReader idxReader = new BinaryReader(idxStream);
BinaryWriter idxWriter = new BinaryWriter(idxStream);
BinaryWriter mulWriter = new BinaryWriter(mulStream);
foreach (DeltaState state in table.Values)
{
StaticTile[] oldTiles = ReadStaticBlock(idxReader, mulStream, state.m_X, state.m_Y,
matrix.BlockWidth, matrix.BlockHeight, out int oldTileCount);
if (oldTileCount < 0)
continue;
int newTileCount = 0;
StaticTile[] newTiles = new StaticTile[state.m_List.Count];
for (int i = 0; i < state.m_List.Count; ++i)
{
Item item = state.m_List[i];
int xOffset = item.X - state.m_X * 8;
int yOffset = item.Y - state.m_Y * 8;
if (xOffset < 0 || xOffset >= 8 || yOffset < 0 || yOffset >= 8)
continue;
StaticTile newTile = new StaticTile((ushort)item.ItemID, (byte)xOffset, (byte)yOffset,
(sbyte)item.Z, (short)item.Hue);
newTiles[newTileCount++] = newTile;
item.Delete();
++totalFrozen;
}
int mulPos = -1;
int length = -1;
int extra = 0;
if (oldTileCount + newTileCount > 0)
{
mulWriter.Seek(0, SeekOrigin.End);
mulPos = (int)mulWriter.BaseStream.Position;
length = (oldTileCount + newTileCount) * 7;
extra = 1;
for (int i = 0; i < oldTileCount; ++i)
{
StaticTile toWrite = oldTiles[i];
mulWriter.Write((ushort)toWrite.ID);
mulWriter.Write((byte)toWrite.X);
mulWriter.Write((byte)toWrite.Y);
mulWriter.Write((sbyte)toWrite.Z);
mulWriter.Write((short)toWrite.Hue);
}
for (int i = 0; i < newTileCount; ++i)
{
StaticTile toWrite = newTiles[i];
mulWriter.Write((ushort)toWrite.ID);
mulWriter.Write((byte)toWrite.X);
mulWriter.Write((byte)toWrite.Y);
mulWriter.Write((sbyte)toWrite.Z);
mulWriter.Write((short)toWrite.Hue);
}
mulWriter.Flush();
}
int idxPos = (state.m_X * matrix.BlockHeight + state.m_Y) * 12;
idxWriter.Seek(idxPos, SeekOrigin.Begin);
idxWriter.Write(mulPos);
idxWriter.Write(length);
idxWriter.Write(extra);
idxWriter.Flush();
matrix.SetStaticBlock(state.m_X, state.m_Y, null);
}
}
}
}
if (totalFrozen == 0 && badDataFile)
from.SendGump(new NoticeGump(1060637, 30720,
"Output data files could not be opened and the freeze operation has been aborted.<br><br>This probably means your server and client are using the same data files. Instructions on how to resolve this can be found in the first warning window.",
0xFFC000, 320, 240));
else
from.SendGump(new NoticeGump(1060637, 30720,
$"Freeze operation completed successfully.<br><br>{totalFrozen} item{(totalFrozen != 1 ? "s were" : " was")} frozen.<br><br>You must restart your client and update it's data files to see the changes.",
0xFFC000, 320, 240));
}
[Usage("Unfreeze")]
[Description("Makes a targeted area of static items dynamic.")]
public static void Unfreeze_OnCommand(CommandEventArgs e)
{
Mobile from = e.Mobile;
BoundingBoxPicker.Begin(from, (map, start, end) => UnfreezeBox_Callback(from, map, start, end));
}
[Usage("UnfreezeMap")]
[Description("Makes every static item in your map dynamic.")]
public static void UnfreezeMap_OnCommand(CommandEventArgs e)
{
Map map = e.Mobile.Map;
if (map != null && map != Map.Internal)
SendWarning(e.Mobile, "You are about to unfreeze <u>all items in {0}</u>.", BaseUnfreezeWarning, map,
NullP3D, NullP3D, UnfreezeWarning_Callback);
}
[Usage("UnfreezeWorld")]
[Description("Makes every static item on all maps dynamic.")]
public static void UnfreezeWorld_OnCommand(CommandEventArgs e)
{
SendWarning(e.Mobile, "You are about to unfreeze <u>every item on every map</u>.", BaseUnfreezeWarning, null,
NullP3D, NullP3D, UnfreezeWarning_Callback);
}
private static void UnfreezeBox_Callback(Mobile from, Map map, Point3D start, Point3D end)
{
SendWarning(from, "You are about to unfreeze a section of items.", BaseUnfreezeWarning, map, start, end,
UnfreezeWarning_Callback);
}
private static void UnfreezeWarning_Callback(Mobile from, bool okay, StateInfo si)
{
if (!okay)
return;
Unfreeze(from, si.m_Map, si.m_Start, si.m_End);
}
private static void DoUnfreeze(Map map, Point2D start, Point2D end, ref bool badDataFile, ref int totalUnfrozen)
{
start = map.Bound(start);
end = map.Bound(end);
int xStartBlock = start.X >> 3;
int yStartBlock = start.Y >> 3;
int xEndBlock = end.X >> 3;
int yEndBlock = end.Y >> 3;
int xTileStart = start.X, yTileStart = start.Y;
int xTileWidth = end.X - start.X + 1, yTileHeight = end.Y - start.Y + 1;
TileMatrix matrix = map.Tiles;
using (FileStream idxStream = OpenWrite(matrix.IndexStream))
{
using (FileStream mulStream = OpenWrite(matrix.DataStream))
{
if (idxStream == null || mulStream == null)
{
badDataFile = true;
return;
}
BinaryReader idxReader = new BinaryReader(idxStream);
BinaryWriter idxWriter = new BinaryWriter(idxStream);
BinaryWriter mulWriter = new BinaryWriter(mulStream);
for (int x = xStartBlock; x <= xEndBlock; ++x)
for (int y = yStartBlock; y <= yEndBlock; ++y)
{
StaticTile[] oldTiles = ReadStaticBlock(idxReader, mulStream, x, y, matrix.BlockWidth,
matrix.BlockHeight, out int oldTileCount);
if (oldTileCount < 0)
continue;
int newTileCount = 0;
StaticTile[] newTiles = new StaticTile[oldTileCount];
int baseX = (x << 3) - xTileStart, baseY = (y << 3) - yTileStart;
for (int i = 0; i < oldTileCount; ++i)
{
StaticTile oldTile = oldTiles[i];
int px = baseX + oldTile.X;
int py = baseY + oldTile.Y;
if (px < 0 || px >= xTileWidth || py < 0 || py >= yTileHeight)
{
newTiles[newTileCount++] = oldTile;
}
else
{
++totalUnfrozen;
Item item = new Static(oldTile.ID);
item.Hue = oldTile.Hue;
item.MoveToWorld(new Point3D(px + xTileStart, py + yTileStart, oldTile.Z), map);
}
}
int mulPos = -1;
int length = -1;
int extra = 0;
if (newTileCount > 0)
{
mulWriter.Seek(0, SeekOrigin.End);
mulPos = (int)mulWriter.BaseStream.Position;
length = newTileCount * 7;
extra = 1;
for (int i = 0; i < newTileCount; ++i)
{
StaticTile toWrite = newTiles[i];
mulWriter.Write((ushort)toWrite.ID);
mulWriter.Write((byte)toWrite.X);
mulWriter.Write((byte)toWrite.Y);
mulWriter.Write((sbyte)toWrite.Z);
mulWriter.Write((short)toWrite.Hue);
}
mulWriter.Flush();
}
int idxPos = (x * matrix.BlockHeight + y) * 12;
idxWriter.Seek(idxPos, SeekOrigin.Begin);
idxWriter.Write(mulPos);
idxWriter.Write(length);
idxWriter.Write(extra);
idxWriter.Flush();
matrix.SetStaticBlock(x, y, null);
}
}
}
}
public static void DoUnfreeze(Map map, ref bool badDataFile, ref int totalUnfrozen)
{
DoUnfreeze(map, Point2D.Zero, new Point2D(map.Width - 1, map.Height - 1), ref badDataFile, ref totalUnfrozen);
}
public static void Unfreeze(Mobile from, Map map, Point3D start, Point3D end)
{
int totalUnfrozen = 0;
bool badDataFile = false;
if (map == null)
{
CommandLogging.WriteLine(from, "{0} {1} invoking unfreeze for every item in every map", from.AccessLevel,
CommandLogging.Format(from));
DoUnfreeze(Map.Felucca, ref badDataFile, ref totalUnfrozen);
DoUnfreeze(Map.Trammel, ref badDataFile, ref totalUnfrozen);
DoUnfreeze(Map.Ilshenar, ref badDataFile, ref totalUnfrozen);
DoUnfreeze(Map.Malas, ref badDataFile, ref totalUnfrozen);
DoUnfreeze(Map.Tokuno, ref badDataFile, ref totalUnfrozen);
}
else if (start == NullP3D && end == NullP3D)
{
CommandLogging.WriteLine(from, "{0} {1} invoking unfreeze for every item in {2}", from.AccessLevel,
CommandLogging.Format(from), map);
DoUnfreeze(map, ref badDataFile, ref totalUnfrozen);
}
else
{
CommandLogging.WriteLine(from, "{0} {1} invoking unfreeze from {2} to {3} in {4}", from.AccessLevel,
CommandLogging.Format(from), new Point2D(start), new Point2D(end), map);
DoUnfreeze(map, new Point2D(start), new Point2D(end), ref badDataFile, ref totalUnfrozen);
}
if (totalUnfrozen == 0 && badDataFile)
from.SendGump(new NoticeGump(1060637, 30720,
"Output data files could not be opened and the unfreeze operation has been aborted.<br><br>This probably means your server and client are using the same data files. Instructions on how to resolve this can be found in the first warning window.",
0xFFC000, 320, 240));
else
from.SendGump(new NoticeGump(1060637, 30720,
$"Unfreeze operation completed successfully.<br><br>{totalUnfrozen} item{(totalUnfrozen != 1 ? "s were" : " was")} unfrozen.<br><br>You must restart your client and update it's data files to see the changes.",
0xFFC000, 320, 240));
}
private static FileStream OpenWrite(FileStream orig)
{
if (orig == null)
return null;
try
{
return new FileStream(orig.Name, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
}
catch
{
return null;
}
}
private static StaticTile[] ReadStaticBlock(BinaryReader idxReader, FileStream mulStream, int x, int y, int width,
int height, out int count)
{
try
{
if (x < 0 || x >= width || y < 0 || y >= height)
{
count = -1;
return m_TileBuffer;
}
idxReader.BaseStream.Seek((x * height + y) * 12, SeekOrigin.Begin);
int lookup = idxReader.ReadInt32();
int length = idxReader.ReadInt32();
if (lookup < 0 || length <= 0)
{
count = 0;
}
else
{
count = length / 7;
mulStream.Seek(lookup, SeekOrigin.Begin);
if (m_TileBuffer.Length < count)
m_TileBuffer = new StaticTile[count];
StaticTile[] staTiles = m_TileBuffer;
if (m_Buffer == null || length > m_Buffer.Length)
m_Buffer = new byte[length];
mulStream.Read(m_Buffer, 0, length);
int index = 0;
for (int i = 0; i < count; ++i)
staTiles[i].Set((ushort)(m_Buffer[index++] | (m_Buffer[index++] << 8)),
m_Buffer[index++], m_Buffer[index++], (sbyte)m_Buffer[index++],
(short)(m_Buffer[index++] | (m_Buffer[index++] << 8)));
}
}
catch
{
count = -1;
}
return m_TileBuffer;
}
private class DeltaState
{
public List<Item> m_List;
public int m_X, m_Y;
public DeltaState(Point2D p)
{
m_X = p.X;
m_Y = p.Y;
m_List = new List<Item>();
}
}
public class StateInfo
{
public Map m_Map;
public Point3D m_Start, m_End;
public StateInfo(Map map, Point3D start, Point3D end)
{
m_Map = map;
m_Start = start;
m_End = end;
}
}
}
}

View file

@ -0,0 +1,141 @@
using System.Collections.Generic;
using Server.Mobiles;
using Server.Network;
using Server.Targeting;
namespace Server.Commands
{
public class VisibilityList
{
public static void Initialize()
{
EventSink.Login += OnLogin;
CommandSystem.Register("Vis", AccessLevel.Counselor, Vis_OnCommand);
CommandSystem.Register("VisList", AccessLevel.Counselor, VisList_OnCommand);
CommandSystem.Register("VisClear", AccessLevel.Counselor, VisClear_OnCommand);
}
public static void OnLogin(LoginEventArgs e)
{
if (e.Mobile is PlayerMobile pm) pm.VisibilityList.Clear();
}
[Usage("Vis")]
[Description(
"Adds or removes a targeted player from your visibility list. Anyone on your visibility list will be able to see you at all times, even when you're hidden.")]
public static void Vis_OnCommand(CommandEventArgs e)
{
if (e.Mobile is PlayerMobile)
{
e.Mobile.Target = new VisTarget();
e.Mobile.SendMessage("Select person to add or remove from your visibility list.");
}
}
[Usage("VisList")]
[Description("Shows the names of everyone in your visibility list.")]
public static void VisList_OnCommand(CommandEventArgs e)
{
if (e.Mobile is PlayerMobile pm)
{
List<Mobile> list = pm.VisibilityList;
if (list.Count > 0)
{
pm.SendMessage("You are visible to {0} mobile{1}:", list.Count, list.Count == 1 ? "" : "s");
for (int i = 0; i < list.Count; ++i)
pm.SendMessage("#{0}: {1}", i + 1, list[i].Name);
}
else
{
pm.SendMessage("Your visibility list is empty.");
}
}
}
[Usage("VisClear")]
[Description("Removes everyone from your visibility list.")]
public static void VisClear_OnCommand(CommandEventArgs e)
{
if (e.Mobile is PlayerMobile pm)
{
List<Mobile> list = new List<Mobile>(pm.VisibilityList);
pm.VisibilityList.Clear();
pm.SendMessage("Your visibility list has been cleared.");
for (int i = 0; i < list.Count; ++i)
{
Mobile m = list[i];
if (!m.CanSee(pm) && Utility.InUpdateRange(m, pm))
m.Send(pm.RemovePacket);
}
}
}
private class VisTarget : Target
{
public VisTarget() : base(-1, false, TargetFlags.None)
{
}
protected override void OnTarget(Mobile from, object targeted)
{
if (from is PlayerMobile pm && targeted is Mobile targ)
{
if (targ.AccessLevel <= pm.AccessLevel)
{
List<Mobile> list = pm.VisibilityList;
if (list.Contains(targ))
{
list.Remove(targ);
pm.SendMessage("{0} has been removed from your visibility list.", targ.Name);
}
else
{
list.Add(targ);
pm.SendMessage("{0} has been added to your visibility list.", targ.Name);
}
if (Utility.InUpdateRange(targ, from))
{
NetState ns = targ.NetState;
if (ns != null)
{
if (targ.CanSee(pm))
{
ns.Send(MobileIncoming.Create(ns, targ, pm));
if (ObjectPropertyList.Enabled)
{
ns.Send(pm.OPLPacket);
foreach (Item item in pm.Items)
ns.Send(item.OPLPacket);
}
}
else
{
ns.Send(pm.RemovePacket);
}
}
}
}
else
{
pm.SendMessage("They can already see you!");
}
}
else
{
from.SendMessage("Add only mobiles to your visibility list.");
}
}
}
}
}

View file

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using Server.Items;
using Server.Multis;
namespace Server.Commands
{
public class Wipe
{
[Flags]
public enum WipeType
{
Items = 0x01,
Mobiles = 0x02,
Multis = 0x04,
All = Items | Mobiles | Multis
}
public static void Initialize()
{
CommandSystem.Register("Wipe", AccessLevel.GameMaster, WipeAll_OnCommand);
CommandSystem.Register("WipeItems", AccessLevel.GameMaster, WipeItems_OnCommand);
CommandSystem.Register("WipeNPCs", AccessLevel.GameMaster, WipeNPCs_OnCommand);
CommandSystem.Register("WipeMultis", AccessLevel.GameMaster, WipeMultis_OnCommand);
}
[Usage("Wipe")]
[Description("Wipes all items and npcs in a targeted bounding box.")]
private static void WipeAll_OnCommand(CommandEventArgs e)
{
BeginWipe(e.Mobile, WipeType.Items | WipeType.Mobiles);
}
[Usage("WipeItems")]
[Description("Wipes all items in a targeted bounding box.")]
private static void WipeItems_OnCommand(CommandEventArgs e)
{
BeginWipe(e.Mobile, WipeType.Items);
}
[Usage("WipeNPCs")]
[Description("Wipes all npcs in a targeted bounding box.")]
private static void WipeNPCs_OnCommand(CommandEventArgs e)
{
BeginWipe(e.Mobile, WipeType.Mobiles);
}
[Usage("WipeMultis")]
[Description("Wipes all multis in a targeted bounding box.")]
private static void WipeMultis_OnCommand(CommandEventArgs e)
{
BeginWipe(e.Mobile, WipeType.Multis);
}
public static void BeginWipe(Mobile from, WipeType type)
{
BoundingBoxPicker.Begin(from, (map, start, end) => DoWipe(from, map, start, end, type));
}
public static void DoWipe(Mobile from, Map map, Point3D start, Point3D end, WipeType type)
{
CommandLogging.WriteLine(from, "{0} {1} wiping from {2} to {3} in {5} ({4})", from.AccessLevel,
CommandLogging.Format(from), start, end, type, map);
bool mobiles = (type & WipeType.Mobiles) != 0;
bool multis = (type & WipeType.Multis) != 0;
bool items = (type & WipeType.Items) != 0;
List<IEntity> toDelete = new List<IEntity>();
Rectangle2D rect = new Rectangle2D(start.X, start.Y, end.X - start.X + 1, end.Y - start.Y + 1);
IPooledEnumerable<IEntity> eable;
if (!items && !multis || !mobiles)
return;
eable = map.GetObjectsInBounds(rect);
foreach (IEntity obj in eable)
if (items && obj is Item && !(obj is BaseMulti || obj is HouseSign))
toDelete.Add(obj);
else if (multis && obj is BaseMulti)
toDelete.Add(obj);
else if (obj is Mobile mobile && !mobile.Player)
toDelete.Add(mobile);
eable.Free();
for (int i = 0; i < toDelete.Count; ++i)
toDelete[i].Delete();
}
}
}