ModernUO/Projects/UOContent/Engines/XMLSpawner/BaseXmlSpawner.cs
2024-02-12 18:15:12 -08:00

3810 lines
124 KiB
C#

using Server.Commands;
using Server.Items;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using Server.Engines.XmlSpawner2;
namespace Server.Mobiles;
public delegate void XmlGumpCallback(Mobile from, object invoker, string response);
public class BaseXmlSpawner
{
[Flags]
public enum KeywordFlags
{
HoldSpawn = 0x01,
HoldSequence = 0x02,
Serialize = 0x04,
Defrag = 0x08
}
public class TypeInfo
{
public List<PropertyInfo> plist = new(); // hold propertyinfo list
public Type t;
}
private static readonly Type typeofTimeSpan = typeof(TimeSpan);
private static readonly Type typeofCustomEnum = typeof(CustomEnumAttribute);
private static readonly Type[] m_ParseTypes = { typeof(string) };
private static readonly object[] m_ParseParams = new object[1];
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 readonly Type[] m_NumericTypes =
{
typeof(byte), typeof(sbyte),
typeof(short), typeof(ushort),
typeof(int), typeof(uint),
typeof(long), typeof(ulong), typeof(Serial)
};
public static bool IsNumeric(Type t) => Array.IndexOf(m_NumericTypes, t) >= 0;
private static readonly Type typeofType = typeof(Type);
private static bool IsType(Type t) => t == typeofType;
private static readonly Type typeofChar = typeof(char);
private static bool IsChar(Type t) => t == typeofChar;
private static readonly Type typeofString = typeof(string);
private static bool IsString(Type t) => t == typeofString;
private static bool IsEnum(Type t) => t.IsEnum;
private static bool IsCustomEnum(Type t) => t.IsDefined(typeofCustomEnum, false);
private enum typeKeyword
{
SET,
GOTO,
COMMAND,
SPAWN,
DESPAWN
}
private enum typemodKeyword
{
// Preparing for removal.
}
private enum valueKeyword
{
PLAYERSINRANGE,
RANDNAME
}
private enum valuemodKeyword
{
INC,
MOB,
TRIGMOB,
PLAYERSINRANGE
}
// name of mobile used to issue commands via the COMMAND keyword. The accesslevel of the mobile will determine
// the accesslevel of commands that can be issued.
// if this is null, then COMMANDS can only be issued when triggered by players of the appropriate accesslevel
private static readonly string CommandMobileName = null;
private static readonly Dictionary<string, typeKeyword> typeKeywordHash = new();
private static readonly Dictionary<string, typemodKeyword> typemodKeywordHash = new();
private static readonly Dictionary<string, valueKeyword> valueKeywordHash = new();
private static readonly Dictionary<string, valuemodKeyword> valuemodKeywordHash = new();
private static readonly char[] slashdelim = { '/' };
private static readonly char[] commadelim = { ',' };
private static readonly char[] semicolondelim = { ';' };
private static readonly char[] literalend = { '§' };
public static bool IsValueKeyword(string str)
{
if (string.IsNullOrEmpty(str) || !char.IsUpper(str[0]))
{
return false;
}
return valueKeywordHash.ContainsKey(str);
}
public static bool IsValuemodKeyword(string str)
{
if (string.IsNullOrEmpty(str) || !char.IsUpper(str[0]))
{
return false;
}
return valuemodKeywordHash.ContainsKey(str);
}
public static bool IsTypeKeyword(string typeName)
{
if (string.IsNullOrEmpty(typeName) || !char.IsUpper(typeName[0]))
{
return false;
}
return typeKeywordHash.ContainsKey(typeName);
}
public static bool IsTypeOrItemKeyword(string typeName)
{
if (string.IsNullOrEmpty(typeName) || !char.IsUpper(typeName[0]))
{
return false;
}
return typeKeywordHash.ContainsKey(typeName);
}
public static void RemoveKeyword(string name)
{
if (name == null)
{
return;
}
name = name.Trim().ToUpper();
typeKeywordHash.Remove(name);
typemodKeywordHash.Remove(name);
valueKeywordHash.Remove(name);
valuemodKeywordHash.Remove(name);
}
public class KeywordTag
{
public KeywordFlags Flags;
public int Type;
private Timer m_Timer;
public DateTime m_End;
public DateTime m_TimeoutEnd;
public TimeSpan m_Delay;
public TimeSpan m_Timeout;
private XmlSpawner m_Spawner;
public string m_Condition;
public int m_Goto;
public bool Deleted;
public int Serial = -1;
public Mobile m_TrigMob;
public string Typename;
public KeywordTag(string typename, XmlSpawner spawner)
: this(typename, spawner, -1)
{
}
public KeywordTag(string typename, XmlSpawner spawner, int type)
: this(typename, spawner, type, TimeSpan.Zero, TimeSpan.Zero, null, -1)
{
}
public KeywordTag(string typename, XmlSpawner spawner, int type, TimeSpan delay, TimeSpan timeout, string condition, int gotogroup)
{
Type = type;
m_Delay = delay;
m_Timeout = timeout;
m_TimeoutEnd = Core.Now + timeout;
m_Spawner = spawner;
m_Condition = condition;
m_Goto = gotogroup;
Typename = typename;
// add the tag to the list
if (spawner != null && !spawner.Deleted)
{
m_TrigMob = spawner.TriggerMob;
if (spawner.m_KeywordTagList == null)
{
spawner.m_KeywordTagList = new List<KeywordTag>();
}
// calculate the serial index of the new tag by adding one to the last one if there is one, otherwise just reset to 0
if (spawner.m_KeywordTagList.Count > 0)
{
Serial = spawner.m_KeywordTagList[spawner.m_KeywordTagList.Count - 1].Serial + 1;
}
else
{
Serial = 0;
}
spawner.m_KeywordTagList.Add(this);
switch (type)
{
case 0: // WAIT timer type
{
// start up the timer
DoTimer(delay, m_Delay, condition, gotogroup);
Flags |= KeywordFlags.HoldSpawn;
Flags |= KeywordFlags.Serialize;
break;
}
case 1: // GUMP type
{
break;
}
case 2: // GOTO type
{
Flags |= KeywordFlags.HoldSequence;
Flags |= KeywordFlags.Serialize;
break;
}
default:
{
// dont do anything for other types
Flags |= KeywordFlags.Defrag;
break;
}
}
}
}
public void Delete()
{
// and stop all timers
if (m_Timer != null && Type == 0)
{
m_Timer.Stop();
}
Deleted = true;
// and remove it from the list
RemoveFromTagList(m_Spawner, this);
}
private void DoTimer(TimeSpan delay, TimeSpan repeatdelay, string condition, int gotogroup)
{
m_End = Core.Now + delay;
if (m_Timer != null)
{
m_Timer.Stop();
}
m_Timer = new KeywordTimer(m_Spawner, this, delay, repeatdelay, condition, gotogroup);
m_Timer.Start();
}
public void Serialize(IGenericWriter writer)
{
writer.Write(1); // version
// Version 1
writer.Write((int)Flags);
// Version 0
writer.Write(m_Spawner);
writer.Write(Type);
writer.Write(Serial);
if (Type == 0)
{
// save any timer information
writer.Write(m_End - Core.Now);
writer.Write(m_Delay);
writer.Write(m_Condition);
writer.Write(m_Goto);
writer.Write(m_TimeoutEnd - Core.Now);
writer.Write(m_Timeout);
writer.Write(m_TrigMob);
}
}
public void Deserialize(IGenericReader reader)
{
int version = reader.ReadInt();
switch (version)
{
case 1:
{
Flags = (KeywordFlags)reader.ReadInt();
goto case 0;
}
case 0:
{
m_Spawner = reader.ReadEntity<XmlSpawner>();
Type = reader.ReadInt();
Serial = reader.ReadInt();
if (Type == 0)
{
// get any timer info
TimeSpan delay = reader.ReadTimeSpan();
m_Delay = reader.ReadTimeSpan();
m_Condition = reader.ReadString();
m_Goto = reader.ReadInt();
TimeSpan timeoutdelay = reader.ReadTimeSpan();
m_TimeoutEnd = Core.Now + timeoutdelay;
m_Timeout = reader.ReadTimeSpan();
m_TrigMob = reader.ReadEntity<Mobile>();
DoTimer(delay, m_Delay, m_Condition, m_Goto);
}
break;
}
}
}
// added the timer that begins on spawning tmp keywords
private class KeywordTimer : Timer
{
private readonly KeywordTag m_Tag;
private readonly XmlSpawner m_Spawner;
private readonly string m_Condition;
private readonly int m_Goto;
private TimeSpan m_Repeatdelay;
public KeywordTimer(XmlSpawner spawner, KeywordTag tag, TimeSpan delay, TimeSpan repeatdelay, string condition, int gotogroup)
: base(delay)
{
m_Tag = tag;
m_Spawner = spawner;
m_Condition = condition;
m_Goto = gotogroup;
m_Repeatdelay = repeatdelay;
}
protected override void OnTick()
{
// if a condition is available then test it
if (!string.IsNullOrEmpty(m_Condition) && m_Spawner != null && m_Spawner.Running)
{
// if the test is valid then terminate the timer
if (TestItemProperty(m_Spawner, m_Spawner, m_Condition, out _))
{
// spawn the designated subgroup if specified
if (m_Goto >= 0 && m_Spawner != null && !m_Spawner.Deleted)
{
// set the trigmob to the mob that originally triggered the wait keyword
if (m_Tag != null)
{
m_Spawner.TriggerMob = m_Tag.m_TrigMob;
}
// spawn the subgroup
m_Spawner.SpawnSubGroup(m_Goto, 0);
}
// get rid of the temporary tag
if (m_Tag != null && !m_Tag.Deleted)
{
m_Tag.Delete();
}
}
else
{
// otherwise restart it and keep on holding
if (m_Tag != null && !m_Tag.Deleted)
{
// check the timeout if applicable
if (m_Tag.m_Timeout > TimeSpan.Zero && m_Tag.m_TimeoutEnd < Core.Now)
{
// release the hold on spawning and delete the tag
m_Tag.Delete();
}
else
{
m_Tag.DoTimer(m_Repeatdelay, m_Repeatdelay, m_Condition, m_Goto);
}
}
}
}
else
{
// and terminate the timer
if (m_Tag != null && !m_Tag.Deleted)
{
m_Tag.Delete();
}
}
}
}
}
public static string TagInfo(KeywordTag tag)
{
if (tag != null)
{
return $"{tag.Typename} : type={tag.Type} cond={tag.m_Condition} go={tag.m_Goto} del={tag.m_Delay} end={tag.m_End}";
}
return null;
}
public static void RemoveFromTagList(XmlSpawner spawner, KeywordTag tag)
{
for (int i = 0; i < spawner.m_KeywordTagList.Count; i++)
{
if (tag == spawner.m_KeywordTagList[i])
{
spawner.m_KeywordTagList.RemoveAt(i);
break;
}
}
}
public static KeywordTag GetFromTagList(XmlSpawner spawner, int serial)
{
for (int i = 0; i < spawner.m_KeywordTagList.Count; i++)
{
if (serial == spawner.m_KeywordTagList[i].Serial)
{
return spawner.m_KeywordTagList[i];
}
}
return null;
}
private static string InternalGetValue(object o, PropertyInfo p, int index)
{
Type type = p.PropertyType;
object value = null;
if (type.IsPrimitive)
{
value = p.GetValue(o, null);
}
else if (type.GetInterface("IList") != null && index >= 0)
{
try
{
object arrayvalue = p.GetValue(o, null);
value = ((IList<object>)arrayvalue)[index];
}
catch { }
}
else
{
value = p.GetValue(o, null);
}
string toString;
if (value == null)
{
toString = "(-null-)";
}
else if (IsNumeric(type))
{
toString = string.Format("{0} (0x{0:X})", value);
}
else if (IsChar(type))
{
toString = string.Format("'{0}' ({1} [0x{1:X}])", value, (int)value);
}
else if (IsString(type))
{
toString = $"\"{value}\"";
}
else
{
toString = value.ToString();
}
return $"{p.Name} = {toString}";
}
public static bool IsItem(Type type) => type != null && (type == typeof(Item) || type.IsSubclassOf(typeof(Item)));
public static bool IsMobile(Type type) => type != null && (type == typeof(Mobile) || type.IsSubclassOf(typeof(Mobile)));
public static string ConstructFromString(PropertyInfo p, Type type, object obj, string value, ref object constructed)
{
object toSet;
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 (IsCustomEnum(type))
{
try
{
MethodInfo info = p.PropertyType.GetMethod("Parse", new[] { typeof(string) });
if (info != null)
{
toSet = info.Invoke(null, new object[] { value });
}
else if (p.PropertyType == typeof(Enum) || p.PropertyType.IsSubclassOf(typeof(Enum)))
{
toSet = Enum.Parse(p.PropertyType, value, false);
}
else
{
toSet = null;
}
if (toSet == null)
{
return "That is not a valid custom enumeration member.";
}
}
catch
{
return "That is not a valid custom enumeration member.";
}
}
else if (IsType(type))
{
try
{
toSet = AssemblyHandler.FindTypeByName(value);
if (toSet == null)
{
return "No type with that name was found.";
}
}
catch
{
return "No type with that name was found.";
}
}
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. not convertible.";
}
}
else if (value.StartsWith("0x") && (IsItem(type) || IsMobile(type)))
{
try
{
// parse out the mobile or item name from the value string
int ispace = value.IndexOf(' ');
string valstr = value.Substring(2);
if (ispace > 0)
{
valstr = value.Substring(2, ispace - 2);
}
toSet = World.FindEntity((Serial)Convert.ToUInt32(valstr, 16));
// now check to make sure the object returned is consistent with the type
if (!(toSet is Mobile && IsMobile(type) || toSet is Item && IsItem(type)))
{
return "Item/Mobile type mismatch. cannot assign.";
}
}
catch
{
return "That is not properly formatted. not convertible.";
}
}
else if (type.GetInterface("IList") != null)
{
try
{
object arrayvalue = p.GetValue(obj, null);
object po = ((IList<object>)arrayvalue)[0];
Type atype = po.GetType();
toSet = Parse(obj, atype, value);
}
catch
{
return "That is not properly formatted.";
}
}
else
{
try
{
toSet = Convert.ChangeType(value, type);
}
catch
{
return "That is not properly formatted.";
}
}
constructed = toSet;
return null;
}
public static string InternalSetValue(Mobile from, object o, PropertyInfo p, string value, bool shouldLog, int index)
{
object toSet = null;
Type ptype = p.PropertyType;
string result = ConstructFromString(p, p.PropertyType, o, value, ref toSet);
if (result != null)
{
return result;
}
try
{
if (shouldLog)
{
CommandLogging.LogChangeProperty(from, o, p.Name, value);
}
if (ptype.IsPrimitive)
{
p.SetValue(o, toSet, null);
}
else if (ptype.GetInterface("IList") != null && index >= 0)
{
try
{
object arrayvalue = p.GetValue(o, null);
((IList<object>)arrayvalue)[index] = toSet;
}
catch { }
}
else
{
p.SetValue(o, toSet, null);
}
return "Property has been set.";
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
return "An exception was caught, the property may not be set.";
}
}
// set property values with support for nested attributes
public static string SetPropertyValue(XmlSpawner spawner, object o, string name, string value)
{
if (o == null)
{
return "Null object";
}
Type type = o.GetType();
PropertyInfo[] props = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);
// parse the strings of the form property.attribute into two parts
// first get the property
string[] arglist = ParseString(name, 2, ".");
string propname = arglist[0];
// do a bit of parsing to handle array references
string[] arraystring = propname.Split('[');
int index = 0;
if (arraystring.Length > 1)
{
// parse the property name from the indexing
propname = arraystring[0];
// then parse to get the index value
string[] arrayvalue = arraystring[1].Split(']');
if (arrayvalue.Length > 0)
{
int.TryParse(arraystring[0], out index);
}
}
if (arglist.Length == 2)
{
PropertyInfo plookup = LookupPropertyInfo(spawner, type, propname);
object po;
if (plookup != null)
{
po = plookup.GetValue(o, null);
// now set the nested attribute using the new property list
return SetPropertyValue(spawner, po, arglist[1], value);
}
// is a nested property with attributes so first get the property
foreach (PropertyInfo p in props)
{
if (p.Name.InsensitiveEquals(propname))
{
po = p.GetValue(o, null);
// now set the nested attribute using the new property list
return SetPropertyValue(spawner, po, arglist[1], value);
}
}
}
else
{
// its just a simple single property
PropertyInfo plookup = LookupPropertyInfo(spawner, type, propname);
if (plookup != null)
{
if (!plookup.CanWrite)
{
return "Property is read only.";
}
string returnvalue = InternalSetValue(null, o, plookup, value, false, index);
return returnvalue;
}
// note, looping through all of the props turns out to be a significant performance bottleneck
// good place for optimization
foreach (PropertyInfo p in props)
{
if (p.Name.InsensitiveEquals(propname))
{
if (!p.CanWrite)
{
return "Property is read only.";
}
string returnvalue = InternalSetValue(null, o, p, value, false, index);
return returnvalue;
}
}
}
return "Property not found.";
}
public static string SetPropertyObject(XmlSpawner spawner, object o, string name, object value)
{
if (o == null)
{
return "Null object";
}
Type type = o.GetType();
PropertyInfo[] props = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);
// parse the strings of the form property.attribute into two parts
// first get the property
string[] arglist = ParseString(name, 2, ".");
if (arglist.Length == 2)
{
// is a nested property with attributes so first get the property
// use the lookup table for optimization if possible
PropertyInfo plookup = LookupPropertyInfo(spawner, type, arglist[0]);
object po;
if (plookup != null)
{
po = plookup.GetValue(o, null);
// now set the nested attribute using the new property list
return SetPropertyObject(spawner, po, arglist[1], value);
}
foreach (PropertyInfo p in props)
{
if (p.Name.InsensitiveEquals(arglist[0]))
{
po = p.GetValue(o, null);
// now set the nested attribute using the new property list
return SetPropertyObject(spawner, po, arglist[1], value);
}
}
}
else
{
// its just a simple single property
// use the lookup table for optimization if possible
PropertyInfo plookup = LookupPropertyInfo(spawner, type, name);
if (plookup != null)
{
if (!plookup.CanWrite)
{
return "Property is read only.";
}
if (plookup.PropertyType == typeof(Mobile))
{
plookup.SetValue(o, value, null);
return "Property has been set.";
}
return "Property is not of type Mobile.";
}
foreach (PropertyInfo p in props)
{
if (p.Name.InsensitiveEquals(name))
{
if (!p.CanWrite)
{
return "Property is read only.";
}
if (p.PropertyType == typeof(Mobile))
{
p.SetValue(o, value, null);
return "Property has been set.";
}
return "Property is not of type Mobile.";
}
}
}
return "Property not found.";
}
public static string GetPropertyValue(XmlSpawner spawner, object o, string name, out Type ptype)
{
ptype = null;
if (o == null || name == null)
{
return null;
}
Type type = o.GetType();
object po = null;
PropertyInfo[] props;
try
{
props = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);
}
catch
{
Console.WriteLine("GetProperties error with type {0}", type);
return null;
}
// parse the strings of the form property.attribute into two parts
// first get the property
string[] arglist = ParseString(name, 2, ".");
string propname = arglist[0];
// parse up to 4 comma separated args for special keyword properties
string[] keywordargs = ParseString(propname, 4, ",");
if (keywordargs[0] == "SERIAL")
{
try
{
if (o is Mobile mobile)
{
ptype = mobile.Serial.GetType();
return $"Serial = {mobile.Serial}";
}
if (o is Item item)
{
ptype = item.Serial.GetType();
return $"Serial = {item.Serial}";
}
return "Object is not item/mobile";
}
catch { return "Serial not found."; }
}
if (keywordargs[0] == "TYPE")
{
ptype = typeof(Type);
return $"Type = {o.GetType().Name}";
}
// do a bit of parsing to handle array references
string[] arraystring = arglist[0].Split('[');
int index = -1;
if (arraystring.Length > 1)
{
// parse the property name from the indexing
propname = arraystring[0];
// then parse to get the index value
string[] arrayvalue = arraystring[1].Split(']');
if (arrayvalue.Length > 0)
{
if (!int.TryParse(arrayvalue[0], out index))
{
index = -1;
}
}
}
if (arglist.Length == 2)
{
// use the lookup table for optimization if possible
PropertyInfo plookup = LookupPropertyInfo(spawner, type, propname);
if (plookup != null)
{
if (!plookup.CanRead)
{
return "Property is write only.";
}
ptype = plookup.PropertyType;
if (ptype.IsPrimitive)
{
po = plookup.GetValue(o, null);
}
else if (ptype.GetInterface("IList") != null && index >= 0)
{
try
{
object arrayvalue = plookup.GetValue(o, null);
po = ((IList<object>)arrayvalue)[index];
}
catch { }
}
else
{
po = plookup.GetValue(o, null);
}
// now set the nested attribute using the new property list
return GetPropertyValue(spawner, po, arglist[1], out ptype);
}
// is a nested property with attributes so first get the property
foreach (PropertyInfo p in props)
{
//if (Insensitive.Equals(p.Name, arglist[0]))
if (p.Name.InsensitiveEquals(propname))
{
if (!p.CanRead)
{
return "Property is write only.";
}
ptype = p.PropertyType;
if (ptype.IsPrimitive)
{
po = p.GetValue(o, null);
}
else if (ptype.GetInterface("IList") != null && index >= 0)
{
try
{
object arrayvalue = p.GetValue(o, null);
po = ((IList<object>)arrayvalue)[index];
}
catch { }
}
else
{
po = p.GetValue(o, null);
}
// now set the nested attribute using the new property list
return GetPropertyValue(spawner, po, arglist[1], out ptype);
}
}
}
else
{
// use the lookup table for optimization if possible
PropertyInfo plookup = LookupPropertyInfo(spawner, type, propname);
if (plookup != null)
{
if (!plookup.CanRead)
{
return "Property is write only.";
}
ptype = plookup.PropertyType;
return InternalGetValue(o, plookup, index);
}
// its just a simple single property
foreach (PropertyInfo p in props)
{
//if (Insensitive.Equals(p.Name, name))
if (p.Name.InsensitiveEquals(propname))
{
if (!p.CanRead)
{
return "Property is write only.";
}
ptype = p.PropertyType;
return InternalGetValue(o, p, index);
}
}
}
return "Property not found.";
}
// added in arg parsing to handle object property setting
public static bool ApplyObjectStringProperties(XmlSpawner spawner, string str, object o, Mobile trigmob, object refobject, out string status_str)
{
status_str = null;
if (str == null || str.Length <= 0 || o == null)
{
return false;
}
// object strings will be of the form "object/modifier" where the modifier string is of the form "propname/value/propname/value/..."
// some keywords do not have value arguments so the modifier could take the form "propname/propname/value/..."
// this is handled by parsing into both forms
// make sure the string is properly terminated to assure proper parsing of any final keywords
bool terminated = false;
str = str.Trim();
if (str[str.Length - 1] != '/')
{
str += "/";
terminated = true;
}
var arglist = ParseSlashArgs(str, 2);
string remainder = null;
// place the modifier section of the string in remainder
if (arglist.Length > 1)
{
remainder = arglist[1];
}
bool no_error = true;
// process the modifier string if there is anything
while (arglist.Length > 1)
{
// place into arglist the parsed modifier up to this point
// arglist[0] will contain the propname
// arglist[1] will contain the value
// arglist[2] will contain the reset of the modifier
arglist = ParseSlashArgs(remainder, 3);
// singlearglist will contain the propname and the remainder
// for those keywords that do not have value args
string[] singlearglist = ParseSlashArgs(remainder, 2);
if (arglist.Length > 1)
{
// handle value keywords that may take comma args
// itemarglist[1] will contain arg2/arg3/arg4>/arg5
// additemstr should have the full list of args <arg2/arg3/arg4>/arg5 if they are there. In the case of /arg1/ADD/arg2
// it will just have arg2
string[] groupedarglist = ParseString(arglist[1], 2, "[");
string groupargstring = null;
if (groupedarglist.Length > 1)
{
// take that argument list that should like like arg2/ag3/arg4>/arg5
// need to find the matching ">"
string[] groupargs = ParseToMatchingParen(groupedarglist[1], '[', ']');
// and get the first part of the string without the > so itemargs[0] should be arg2/ag3/arg4
groupargstring = groupargs[0];
}
// need to handle comma args that may be grouped with the () such as the (ATTACHMENT,args) arg
//string[] value_keywordargs = ParseString(groupedarglist[0],10,",");
string[] value_keywordargs = groupedarglist[0].Trim().Split(',');
if (!string.IsNullOrEmpty(groupargstring))
{
if (value_keywordargs != null && value_keywordargs.Length > 0)
{
value_keywordargs[value_keywordargs.Length - 1] = groupargstring;
}
}
// this quick optimization can determine whether this is a regular prop/value assignment
// since most prop modification strings will use regular propnames and not keywords, it makes sense to check for that first
if (value_keywordargs[0].Length > 0 && !char.IsUpper(value_keywordargs[0][0]) &&
arglist[0].Length > 0 && !char.IsUpper(arglist[0][0]))
{
// all of this code is also included in the keyword candidate tests
// this is because regular props can also be entered with uppercase so the lowercase test is not definitive
{
// check for the literal char
if (singlearglist[1] != null && singlearglist[1].Length > 0 && singlearglist[1][0] == '@')
{
//support for literal terminator
singlearglist = ParseLiteralTerminator(singlearglist[1]);
string lstr = singlearglist[0];
if (terminated && lstr[lstr.Length - 1] == '/')
{
lstr = lstr.Remove(lstr.Length - 1, 1);
}
string result = SetPropertyValue(spawner, o, arglist[0], lstr.Remove(0, 1));
// see if it was successful
if (result != "Property has been set.")
{
status_str = $"{arglist[0]} : {result}";
no_error = false;
}
if (singlearglist.Length > 1 && singlearglist[1] != null)
{
remainder = singlearglist[1];
}
else
{
break;
}
}
else
{
string result = SetPropertyValue(spawner, o, arglist[0], arglist[1]);
// see if it was successful
if (result != "Property has been set.")
{
status_str = $"{arglist[0]} : {result}";
no_error = false;
}
if (arglist.Length < 3)
{
break;
}
remainder = arglist[2];
}
}
}
else
{
if (IsValuemodKeyword(value_keywordargs[0]))
{
valuemodKeyword kw = valuemodKeywordHash[value_keywordargs[0]];
if (kw == valuemodKeyword.INC)
{
// increment the property value by the amount. Use the format propname/INC,min,max/ or propname/INC,value
if (value_keywordargs.Length > 1)
{
// get a random number
string incvalue = "0";
if (value_keywordargs.Length > 2)
{
int min, max;
if (int.TryParse(value_keywordargs[1], out min) && int.TryParse(value_keywordargs[2], out max))
{
incvalue = $"{Utility.RandomMinMax(min, max)}";
}
else { status_str = $"Invalid INC args : {arglist[1]}"; no_error = false; }
}
else
{
incvalue = value_keywordargs[1];
}
// get the current property value
Type ptype;
string tmpvalue = GetPropertyValue(spawner, o, arglist[0], out ptype);
// see if it was successful
if (ptype == null)
{
status_str = $"Cant find {arglist[0]}";
no_error = false;
}
else
{
string currentvalue = "0";
try
{
string[] arglist2 = ParseString(tmpvalue, 2, "=");
string[] arglist3 = ParseString(arglist2[1], 2, " ");
currentvalue = arglist3[0].Trim();
}
catch { }
string tmpstr = currentvalue;
// should use the actual ptype info to do the addition. Maybe later.
double d0, d1;
if (double.TryParse(currentvalue, NumberStyles.Any, CultureInfo.InvariantCulture, out d0) && double.TryParse(incvalue, NumberStyles.Any, CultureInfo.InvariantCulture, out d1))
{
tmpstr = ((int)(d0 + d1)).ToString();
}
else
{ status_str = $"Invalid INC args : {arglist[1]}"; no_error = false; }
// set the property value using the incremented value
string result = SetPropertyValue(spawner, o, arglist[0], tmpstr);
// see if it was successful
if (result != "Property has been set.")
{
status_str = $"{arglist[0]} : {result}";
no_error = false;
}
}
}
else
{
status_str = $"Invalid INC args : {arglist[1]}";
no_error = false;
}
if (arglist.Length < 3)
{
break;
}
remainder = arglist[2];
}
else if (kw == valuemodKeyword.MOB)
{
// lookup the mob id based on the name. format is /MOB,name[,type]/
if (value_keywordargs.Length > 1)
{
string typestr = null;
if (value_keywordargs.Length > 2)
{
typestr = value_keywordargs[2];
}
// lookup the name
Mobile mob_id = null;
try
{
mob_id = FindMobileByName(spawner, value_keywordargs[1], typestr); // the format of this will be 0xvalue "name"
}
catch { status_str = $"Invalid MOB args : {arglist[1]}"; no_error = false; }
// set the property value using this format (M) id name
string result = SetPropertyObject(spawner, o, arglist[0], mob_id);
// see if it was successful
if (result != "Property has been set.")
{
status_str = $"{arglist[0]} : {result}";
no_error = false;
}
}
else
{
no_error = false;
}
if (arglist.Length < 3)
{
break;
}
remainder = arglist[2];
}
else if (kw == valuemodKeyword.TRIGMOB)
{
string result = SetPropertyObject(spawner, o, arglist[0], trigmob);
// see if it was successful
if (result != "Property has been set.")
{
status_str = $"{arglist[0]} : {result}";
no_error = false;
}
if (arglist.Length < 3)
{
break;
}
remainder = arglist[2];
}
else if (kw == valuemodKeyword.PLAYERSINRANGE)
{
// syntax is PLAYERSINRANGE,range
int nplayers = 0;
int range = 0;
// get the number of players in range
if (value_keywordargs.Length > 1)
{
int.TryParse(value_keywordargs[1], out range);
}
// count nearby players
if (refobject is Item item)
{
IPooledEnumerable ie = item.GetMobilesInRange(range);
foreach (Mobile p in ie)
{
if (p.Player && p.AccessLevel == AccessLevel.Player)
{
nplayers++;
}
}
ie.Free();
}
else if (refobject is Mobile mobile)
{
IPooledEnumerable ie = mobile.GetMobilesInRange(range);
foreach (Mobile p in ie)
{
if (p.Player && p.AccessLevel == AccessLevel.Player)
{
nplayers++;
}
}
ie.Free();
}
string result = SetPropertyValue(spawner, o, arglist[0], nplayers.ToString());
// see if it was successful
if (result != "Property has been set.")
{
status_str = $"{arglist[0]} : {result}";
no_error = false;
}
if (arglist.Length < 3)
{
break;
}
remainder = arglist[2];
}
}
else
{
// check for the literal char
if (singlearglist[1] != null && singlearglist[1].Length > 0 && singlearglist[1][0] == '@')
{
//support for literal terminator
singlearglist = ParseLiteralTerminator(singlearglist[1]);
string lstr = singlearglist[0];
if (terminated && lstr[lstr.Length - 1] == '/')
{
lstr = lstr.Remove(lstr.Length - 1, 1);
}
string result = SetPropertyValue(spawner, o, arglist[0], lstr.Remove(0, 1));
// see if it was successful
if (result != "Property has been set.")
{
status_str = $"{arglist[0]} : {result}";
no_error = false;
}
if (singlearglist.Length > 1 && singlearglist[1] != null)
{
remainder = singlearglist[1];
}
else
{
break;
}
}
else
{
string result = SetPropertyValue(spawner, o, arglist[0], arglist[1]);
// see if it was successful
if (result != "Property has been set.")
{
status_str = $"{arglist[0]} : {result}";
no_error = false;
}
if (arglist.Length < 3)
{
break;
}
remainder = arglist[2];
}
}
}
}
}
return no_error;
}
public static bool TestMobProperty(XmlSpawner spawner, Mobile mobile, string testString, out string status_str)
{
status_str = null;
// now make sure the mobile itself is there
if (mobile == null || mobile.Deleted)
{
return false;
}
bool testreturn = CheckPropertyString(spawner, mobile, testString, out status_str);
return testreturn;
}
public static bool TestItemProperty(XmlSpawner spawner, Item ObjectPropertyItem, string testString, out string status_str)
{
// now make sure the item itself is there
if (ObjectPropertyItem == null || ObjectPropertyItem.Deleted)
{
status_str = "Trigger Object not found";
return false;
}
bool testreturn = CheckPropertyString(spawner, ObjectPropertyItem, testString, out status_str);
return testreturn;
}
public static PropertyInfo LookupPropertyInfo(XmlSpawner spawner, Type type, string propname)
{
if (spawner == null || type == null || propname == null)
{
return null;
}
// look up the info in the current list
if (spawner.PropertyInfoList == null)
{
spawner.PropertyInfoList = new List<TypeInfo>();
}
PropertyInfo pinfo = null;
TypeInfo tinfo = null;
foreach (TypeInfo to in spawner.PropertyInfoList)
{
// check the type
if (to.t == type)
{
// found it
tinfo = to;
// now search the property list
foreach (PropertyInfo p in to.plist)
{
if (p.Name.InsensitiveEquals(propname))
{
pinfo = p;
}
}
}
}
// did we find the property?
if (pinfo != null)
{
return pinfo;
}
// if it cant be found, then do the full search and add it to the list
PropertyInfo[] props = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo p in props)
{
if (p.Name.InsensitiveEquals(propname))
{
// did we find the type at least?
if (tinfo == null)
{
// if not then add the type to the list
tinfo = new TypeInfo
{
t = type
};
spawner.PropertyInfoList.Add(tinfo);
}
// and add the property to the tinfo property list
tinfo.plist.Add(p);
return p;
}
}
return null;
}
public static string ParseForKeywords(XmlSpawner spawner, object o, string valstr, bool literal, out Type ptype)
{
ptype = null;
if (valstr == null || valstr.Length <= 0)
{
return null;
}
string str = valstr.Trim();
// look for keywords
// need to handle the case of nested arglists like arg,arg,<arg,arg>
// handle value keywords that may take comma args
// itemarglist[1] will contain arg2/arg3/arg4>/arg5
// additemstr should have the full list of args <arg2/arg3/arg4>/arg5 if they are there. In the case of /arg1/ADD/arg2
// it will just have arg2
string[] groupedarglist = ParseString(str, 2, "[");
string groupargstring = null;
if (groupedarglist.Length > 1)
{
// take that argument list that should like like arg2/ag3/arg4>/arg5
// need to find the matching ">"
string[] groupargs = ParseToMatchingParen(groupedarglist[1], '[', ']');
// and get the first part of the string without the > so itemargs[0] should be arg2/ag3/arg4
groupargstring = groupargs[0];
}
// need to handle comma args that may be grouped with the () such as the (ATTACHMENT,args) arg
string[] arglist = groupedarglist[0].Trim().Split(',');
if (!string.IsNullOrEmpty(groupargstring) && arglist.Length > 0)
{
arglist[arglist.Length - 1] = groupargstring;
}
string pname = arglist[0].Trim();
char startc = str[0];
// first see whether it is a standard numeric value
if (startc == '.' || startc == '-' || startc == '+' || startc >= '0' && startc <= '9')
{
// determine the type
ptype = str.IndexOf(".") >= 0 ? typeof(double) : typeof(int);
return str;
}
if (startc == '"' || startc == '(')
{
ptype = typeof(string);
return str;
}
if (startc == '#')
{
ptype = typeof(string);
return str.Substring(1);
}
// or a bool
if (str.ToLower() == "true" || str.ToLower() == "false")
{
ptype = typeof(bool);
return str;
}
// then look for a keyword
if (IsValueKeyword(pname))
{
valueKeyword kw = valueKeywordHash[pname];
if (kw == valueKeyword.PLAYERSINRANGE && arglist.Length > 1)
{
// syntax is PLAYERSINRANGE,range
ptype = typeof(int);
int nplayers = 0;
int range;
// get the number of players in range
int.TryParse(arglist[1], out range);
// count nearby players
if (spawner?.SpawnRegion != null && range < 0)
{
foreach (Mobile p in spawner.SpawnRegion.GetPlayers())
{
if (p.AccessLevel <= spawner.TriggerAccessLevel)
{
nplayers++;
}
}
}
else if (o is Item item)
{
IPooledEnumerable ie = item.GetMobilesInRange(range);
foreach (Mobile p in ie)
{
if (p.Player && p.AccessLevel == AccessLevel.Player)
{
nplayers++;
}
}
ie.Free();
}
else if (o is Mobile mobile)
{
IPooledEnumerable ie = mobile.GetMobilesInRange(range);
foreach (Mobile p in ie)
{
if (p.Player && p.AccessLevel == AccessLevel.Player)
{
nplayers++;
}
}
ie.Free();
}
return nplayers.ToString();
}
if (kw == valueKeyword.RANDNAME && arglist.Length > 1)
{
// syntax is RANDNAME,nametype
return NameList.RandomName(arglist[1]);
}
// an invalid keyword format will be passed as literal
return str;
}
if (literal)
{
ptype = typeof(string);
return str;
}
// otherwise treat it as a property name
string result = GetPropertyValue(spawner, o, pname, out ptype);
return ParseGetValue(result, ptype);
}
public static string ParseGetValue(string str, Type ptype)
{
// the results of getPropertyValue takes the form
// propname = value
// or
// propname = value (hexvalue)
if (str == null)
{
return null;
}
// find the separator
string[] arglist = str.Split("=".ToCharArray(), 2);
if (arglist.Length > 1)
{
if (IsNumeric(ptype))
{
// parse the value portion and get rid of the possible (hexvalue) portion of the string
string[] arglist2 = arglist[1].Trim().Split(" ".ToCharArray(), 2);
return arglist2[0];
}
// for everything else
// pass on as is
return arglist[1].Trim();
}
return null;
}
public static bool CheckPropertyString(XmlSpawner spawner, object o, string testString, out string status_str)
{
status_str = null;
if (o == null)
{
return false;
}
if (string.IsNullOrEmpty(testString))
{
status_str = "Null property test string";
return false;
}
// parse the property test string for and(&)/or(|) operators
string[] arglist = ParseString(testString, 2, "&|");
if (arglist.Length < 2)
{
bool returnval = CheckSingleProperty(spawner, o, testString, out status_str);
// simple conditional test with no and/or operators
return returnval;
}
// test each half independently and combine the results
bool first = CheckSingleProperty(spawner, o, arglist[0], out status_str);
// this will recursively parse the property test string with implicit nesting for multiple logical tests of the
// form A * B * C * D being grouped as A * (B * (C * D))
bool second = CheckPropertyString(spawner, o, arglist[1], out status_str);
int andposition = testString.IndexOf("&");
int orposition = testString.IndexOf("|");
// combine them based upon the operator
if (andposition > 0 && orposition <= 0 || andposition > 0 && andposition < orposition)
{
// and operator
return first && second;
}
if (orposition > 0 && andposition <= 0 || orposition > 0 && orposition < andposition)
{
// or operator
return first || second;
}
// should never get here
return false;
}
public static bool CheckSingleProperty(XmlSpawner spawner, object o, string testString, out string status_str)
{
status_str = null;
if (o == null || testString == null || testString.Length == 0)
{
return false;
}
//get the prop name and test value
// format will be prop=prop, or prop>prop, prop<prop, prop!prop
// also support the 'not' operator ~ at the beginning of a test, like ~prop=prop
testString = testString.Trim();
bool invertreturn = false;
if (testString.Length > 0 && testString[0] == '~')
{
invertreturn = true;
testString = testString.Substring(1, testString.Length - 1);
}
string[] arglist = ParseString(testString, 2, "=><!");
if (arglist.Length < 2)
{
status_str = $"invalid property string : {testString}";
return false;
}
bool hasequal = false;
bool hasnotequals = false;
bool hasgreaterthan = false;
bool haslessthan = false;
if (testString.IndexOf("=") > 0)
{
hasequal = true;
}
else
if (testString.IndexOf("!") > 0)
{
hasnotequals = true;
}
else
if (testString.IndexOf(">") > 0)
{
hasgreaterthan = true;
}
else
if (testString.IndexOf("<") > 0)
{
haslessthan = true;
}
// does it have a valid operator?
if (!hasequal && !hasgreaterthan && !haslessthan && !hasnotequals)
{
return false;
}
Type ptype1;
Type ptype2;
string value1 = ParseForKeywords(spawner, o, arglist[0].Trim(), false, out ptype1);
// see if it was successful
if (ptype1 == null)
{
status_str = $"{arglist[0]} : {value1}";
return invertreturn;
//return false;
}
string value2 = ParseForKeywords(spawner, o, arglist[1].Trim(), false, out ptype2);
// see if it was successful
if (ptype2 == null)
{
status_str = $"{arglist[1]} : {value2}";
return invertreturn;
//return false;
}
// look for hex numeric specifications
int base1 = 10;
int base2 = 10;
if (IsNumeric(ptype1) && !string.IsNullOrEmpty(value1) && value1.StartsWith("0x"))
{
base1 = 16;
}
if (IsNumeric(ptype2) && !string.IsNullOrEmpty(value2) && value2.StartsWith("0x"))
{
base2 = 16;
}
// and do the type dependent comparisons
if (ptype2 == typeof(TimeSpan) || ptype1 == typeof(TimeSpan))
{
if (hasequal)
{
TimeSpan ts1, ts2;
if (TimeSpan.TryParse(value1, out ts1) && TimeSpan.TryParse(value2, out ts2))
{
if (ts1 == ts2)
{
return !invertreturn;
}
}
else
{
status_str = $"invalid timespan comparison : {{0}}{testString}";
}
}
else if (hasnotequals)
{
TimeSpan ts1, ts2;
if (TimeSpan.TryParse(value1, out ts1) && TimeSpan.TryParse(value2, out ts2))
{
if (ts1 != ts2)
{
return !invertreturn;
}
}
else
{
status_str = $"invalid timespan comparison : {{0}}{testString}";
}
}
else if (hasgreaterthan)
{
TimeSpan ts1, ts2;
if (TimeSpan.TryParse(value1, out ts1) && TimeSpan.TryParse(value2, out ts2))
{
if (ts1 > ts2)
{
return !invertreturn;
}
}
else
{
status_str = $"invalid timespan comparison : {{0}}{testString}";
}
}
else
{
TimeSpan ts1, ts2;
if (TimeSpan.TryParse(value1, out ts1) && TimeSpan.TryParse(value2, out ts2))
{
if (ts1 < ts2)
{
return !invertreturn;
}
}
else
{
status_str = $"invalid timespan comparison : {{0}}{testString}";
}
}
}
else
// and do the type dependent comparisons
if (ptype2 == typeof(DateTime) || ptype1 == typeof(DateTime))
{
if (hasequal)
{
DateTime dt1, dt2;
if (DateTime.TryParse(value1, out dt1) && DateTime.TryParse(value2, out dt2))
{
if (dt1 == dt2)
{
return !invertreturn;
}
}
else
{
status_str = $"invalid DateTime comparison : {{0}}{testString}";
}
}
else if (hasnotequals)
{
DateTime dt1, dt2;
if (DateTime.TryParse(value1, out dt1) && DateTime.TryParse(value2, out dt2))
{
if (dt1 != dt2)
{
return !invertreturn;
}
}
else
{
status_str = $"invalid DateTime comparison : {{0}}{testString}";
}
}
else if (hasgreaterthan)
{
DateTime dt1, dt2;
if (DateTime.TryParse(value1, out dt1) && DateTime.TryParse(value2, out dt2))
{
if (dt1 > dt2)
{
return !invertreturn;
}
}
else
{
status_str = $"invalid DateTime comparison : {{0}}{testString}";
}
}
else
{
DateTime dt1, dt2;
if (DateTime.TryParse(value1, out dt1) && DateTime.TryParse(value2, out dt2))
{
if (dt1 < dt2)
{
return !invertreturn;
}
}
else
{
status_str = $"invalid DateTime comparison : {{0}}{testString}";
}
}
}
else if (IsNumeric(ptype2) && IsNumeric(ptype1))
{
if (hasequal)
{
try
{
if (Convert.ToInt64(value1, base1) == Convert.ToInt64(value2, base2))
{
return !invertreturn;
}
}
catch
{
status_str = $"invalid int comparison : {{0}}{testString}";
}
}
else if (hasnotequals)
{
try
{
if (Convert.ToInt64(value1, base1) != Convert.ToInt64(value2, base2))
{
return !invertreturn;
}
}
catch
{
status_str = $"invalid int comparison : {{0}}{testString}";
}
}
else if (hasgreaterthan)
{
try
{
if (Convert.ToInt64(value1, base1) > Convert.ToInt64(value2, base2))
{
return !invertreturn;
}
}
catch { status_str = $"invalid int comparison : {{0}}{testString}"; }
}
else
{
try
{
if (Convert.ToInt64(value1, base1) < Convert.ToInt64(value2, base2))
{
return !invertreturn;
}
}
catch { status_str = $"invalid int comparison : {{0}}{testString}"; }
}
}
else if (ptype2 == typeof(double) && IsNumeric(ptype1))
{
if (hasequal)
{
try
{
if (Convert.ToInt64(value1, base1) == double.Parse(value2))
{
return !invertreturn;
}
}
catch
{
status_str = $"invalid int comparison : {{0}}{testString}";
}
}
else if (hasnotequals)
{
try
{
if (Convert.ToInt64(value1, base1) != double.Parse(value2))
{
return !invertreturn;
}
}
catch
{
status_str = $"invalid int comparison : {{0}}{testString}";
}
}
else if (hasgreaterthan)
{
try
{
if (Convert.ToInt64(value1, base1) > double.Parse(value2))
{
return !invertreturn;
}
}
catch { status_str = $"invalid int comparison : {{0}}{testString}"; }
}
else
{
try
{
if (Convert.ToInt64(value1, base1) < double.Parse(value2))
{
return !invertreturn;
}
}
catch { status_str = $"invalid int comparison : {{0}}{testString}"; }
}
}
else if (ptype1 == typeof(double) && IsNumeric(ptype2))
{
if (hasequal)
{
try
{
if (double.Parse(value1) == Convert.ToInt64(value2, base2))
{
return !invertreturn;
}
}
catch
{
status_str = $"invalid int comparison : {{0}}{testString}";
}
}
else if (hasnotequals)
{
try
{
if (double.Parse(value1) != Convert.ToInt64(value2, base2))
{
return !invertreturn;
}
}
catch
{
status_str = $"invalid int comparison : {{0}}{testString}";
}
}
else if (hasgreaterthan)
{
try
{
if (double.Parse(value1) > Convert.ToInt64(value2, base2))
{
return !invertreturn;
}
}
catch { status_str = $"invalid int comparison : {{0}}{testString}"; }
}
else
{
try
{
if (double.Parse(value1) < Convert.ToInt64(value2, base2))
{
return !invertreturn;
}
}
catch { status_str = $"invalid int comparison : {{0}}{testString}"; }
}
}
else if (ptype1 == typeof(double) && ptype2 == typeof(double))
{
double val1;
double val2;
if (hasequal)
{
if (double.TryParse(value1, NumberStyles.Any, CultureInfo.InvariantCulture, out val1) && double.TryParse(value2, NumberStyles.Any, CultureInfo.InvariantCulture, out val2))
{
if (val1 == val2)
{
return !invertreturn;
}
}
else
{
status_str = $"invalid int comparison : {{0}}{testString}";
}
}
else if (hasnotequals)
{
if (double.TryParse(value1, NumberStyles.Any, CultureInfo.InvariantCulture, out val1) && double.TryParse(value2, NumberStyles.Any, CultureInfo.InvariantCulture, out val2))
{
if (val1 != val2)
{
return !invertreturn;
}
}
else
{
status_str = $"invalid int comparison : {{0}}{testString}";
}
}
else if (hasgreaterthan)
{
if (double.TryParse(value1, NumberStyles.Any, CultureInfo.InvariantCulture, out val1) && double.TryParse(value2, NumberStyles.Any, CultureInfo.InvariantCulture, out val2))
{
if (val1 > val2)
{
return !invertreturn;
}
}
else { status_str = $"invalid int comparison : {{0}}{testString}"; }
}
else
{
if (double.TryParse(value1, NumberStyles.Any, CultureInfo.InvariantCulture, out val1) && double.TryParse(value2, NumberStyles.Any, CultureInfo.InvariantCulture, out val2))
{
if (val1 < val2)
{
return !invertreturn;
}
}
else { status_str = $"invalid int comparison : {{0}}{testString}"; }
}
}
else if (ptype2 == typeof(bool) && ptype1 == typeof(bool))
{
bool val1, val2;
if (hasequal)
{
if (bool.TryParse(value1, out val1) && bool.TryParse(value2, out val2))
{
if (val1 == val2)
{
return !invertreturn;
}
}
else { status_str = $"invalid bool comparison : {{0}}{testString}"; }
}
else if (hasnotequals)
{
if (bool.TryParse(value1, out val1) && bool.TryParse(value2, out val2))
{
if (val1 != val2)
{
return !invertreturn;
}
}
else { status_str = $"invalid bool comparison : {{0}}{testString}"; }
}
}
else if (ptype2 == typeof(double) || ptype2 == typeof(double))
{
double val1;
double val2;
if (hasequal)
{
if (double.TryParse(value1, NumberStyles.Any, CultureInfo.InvariantCulture, out val1) && double.TryParse(value2, NumberStyles.Any, CultureInfo.InvariantCulture, out val2))
{
if (val1 == val2)
{
return !invertreturn;
}
}
else { status_str = $"invalid double comparison : {{0}}{testString}"; }
}
else if (hasnotequals)
{
if (double.TryParse(value1, NumberStyles.Any, CultureInfo.InvariantCulture, out val1) && double.TryParse(value2, NumberStyles.Any, CultureInfo.InvariantCulture, out val2))
{
if (val1 != val2)
{
return !invertreturn;
}
}
else { status_str = $"invalid double comparison : {{0}}{testString}"; }
}
else if (hasgreaterthan)
{
if (double.TryParse(value1, NumberStyles.Any, CultureInfo.InvariantCulture, out val1) && double.TryParse(value2, NumberStyles.Any, CultureInfo.InvariantCulture, out val2))
{
if (val1 > val2)
{
return !invertreturn;
}
}
else { status_str = $"invalid double comparison : {{0}}{testString}"; }
}
else
{
if (double.TryParse(value1, NumberStyles.Any, CultureInfo.InvariantCulture, out val1) && double.TryParse(value2, NumberStyles.Any, CultureInfo.InvariantCulture, out val2))
{
if (val1 < val2)
{
return !invertreturn;
}
}
else { status_str = $"invalid double comparison : {{0}}{testString}"; }
}
}
else
{
// by default just do a string comparison
if (hasequal)
{
if (value1 == value2)
{
return !invertreturn;
}
}
else
if (hasnotequals)
{
if (value1 != value2)
{
return !invertreturn;
}
}
}
return invertreturn;
}
public static Item SearchMobileForItem(Mobile m, string targetName, string typeStr, bool searchbank) => SearchMobileForItem(m, targetName, typeStr, searchbank, false);
public static Item SearchMobileForItem(Mobile m, string targetName, string typeStr, bool searchbank, bool equippedonly)
{
if (m != null && !m.Deleted)
{
// go through all of the items in the pack
List<Item> packlist = m.Items;
for (int i = 0; i < packlist.Count; ++i)
{
Item item = packlist[i];
// dont search bank boxes
if (item is BankBox && !searchbank && !equippedonly)
{
continue;
}
// recursively search containers
if (item != null && !item.Deleted)
{
if (item is Container container && !equippedonly)
{
Item itemTarget = SearchPackForItem(container, targetName, typeStr);
if (itemTarget != null)
{
return itemTarget;
}
}
// test the item name against the trigger string
// if a typestring has been specified then check against that as well
if (CheckNameMatch(targetName, item.Name))
{
if (typeStr == null || CheckType(item, typeStr))
{
//found it
return item;
}
}
}
}
// now check any item that might be held
Item held = m.Holding;
if (held != null && !held.Deleted && !equippedonly)
{
if (held is Container container)
{
Item itemTarget = SearchPackForItem(container, targetName, typeStr);
if (itemTarget != null)
{
return itemTarget;
}
}
// test the item name against the trigger string
if (CheckNameMatch(targetName, held.Name))
{
if (typeStr == null || CheckType(held, typeStr))
{
//found it
return held;
}
}
}
}
return null;
}
public static Item SearchPackForItem(Container pack, string targetName, string typestr)
{
if (pack != null && !pack.Deleted)
{
Type targettype = null;
if (typestr != null)
{
targettype = AssemblyHandler.FindTypeByName(typestr);
}
// go through all of the items in the pack
List<Item> packlist = pack.Items;
for (int i = 0; i < packlist.Count; ++i)
{
Item item = packlist[i];
if (item != null && !item.Deleted)
{
if (item is Container container)
{
Item itemTarget = SearchPackForItem(container, targetName, typestr);
if (itemTarget != null)
{
return itemTarget;
}
}
// test the item name against the trigger string
if (CheckNameMatch(targetName, item.Name))
{
if (targettype == null || item.GetType().Equals(targettype) || item.GetType().IsSubclassOf(targettype))
{
//found it
return item;
}
}
}
}
}
return null;
}
private static bool CheckNameMatch(string targetname, string name) =>
// a "*" targetname will match anything
// a null or empty targetname will match a null name
// otherwise the strings must match
targetname == "*" || name == targetname || targetname != null && targetname.Length == 0 && name == null;
public static bool CheckType(object o, string typename)
{
if (typename == null || o == null)
{
return false;
}
// test the type
Type objecttype = o.GetType();
Type targettype = null;
try
{
targettype = AssemblyHandler.FindTypeByName(typename);
}
catch { }
if (objecttype != null && targettype != null && (objecttype.Equals(targettype) || objecttype.IsSubclassOf(targettype)))
{
return true;
}
return false;
}
public static bool CheckForCarried(Mobile m, string objectivestr)
{
if (m == null || objectivestr == null)
{
return true;
}
// parse the objective string that might be of the form 'obj &| obj &| obj ...'
string[] arglist = ParseString(objectivestr, 2, "&|");
if (arglist.Length < 2)
{
// simple test with no and/or operators
return SingleCheckForCarried(m, objectivestr);
}
// test each half independently and combine the results
bool first = SingleCheckForCarried(m, arglist[0]);
// this will recursively parse the property test string with implicit nesting for multiple logical tests of the
// form A * B * C * D being grouped as A * (B * (C * D))
bool second = CheckForCarried(m, arglist[1]);
int andposition = objectivestr.IndexOf("&");
int orposition = objectivestr.IndexOf("|");
// combine them based upon the operator
if (andposition > 0 && orposition <= 0 || andposition > 0 && andposition < orposition)
{
// and operator
return first && second;
}
if (orposition > 0 && andposition <= 0 || orposition > 0 && orposition < andposition)
{
// or operator
return first || second;
}
// should never get here
return false;
}
public static bool SingleCheckForCarried(Mobile m, string objectivestr)
{
if (m == null || objectivestr == null)
{
return false;
}
bool has_valid_item = false;
// check to see whether there is an objective specification as well. The format is name[,type][,EQUIPPED][,objective,objective,...]
string[] objstr = ParseString(objectivestr, 8, ",");
string itemname = objstr[0];
// check for attachment keyword
if (itemname == "ATTACHMENT")
{
// syntax is ATTACHMENT,name,type
if (objstr.Length > 1)
{
string aname = objstr[1];
Type atype = null;
if (objstr.Length > 2)
{
try
{
atype = AssemblyHandler.FindTypeByName(objstr[2]);
}
catch { }
}
return false;
}
return false;
}
bool equippedonly = false;
string typestr = null;
int objoffset = 1;
// is there a type specification?
while (objoffset < objstr.Length)
{
if (objstr[objoffset] != null && objstr[objoffset].Length > 0)
{
char startc = objstr[objoffset][0];
if (startc >= '0' && startc <= '9')
{
// this is the start of the numeric objective specifications
break;
}
if (objstr[objoffset] == "EQUIPPED")
{
equippedonly = true;
}
else
{
// treat as a type specification if it does not begin with a numeric char
// and is not the EQUIPPED keyword
typestr = objstr[objoffset];
}
}
objoffset++;
}
Item testitem = SearchMobileForItem(m, itemname, typestr, false, equippedonly);
// found the item
if (testitem != null)
{
// is the equippedonly flag set? If so then see if the item is equipped
if (equippedonly && testitem.Parent == m || !equippedonly)
{
has_valid_item = true;
}
}
return has_valid_item;
}
public static bool CheckForNotCarried(Mobile m, string objectivestr)
{
if (m == null || objectivestr == null)
{
return true;
}
// parse the objective string that might be of the form 'obj &| obj &| obj ...'
string[] arglist = ParseString(objectivestr, 2, "&|");
if (arglist.Length < 2)
{
// simple test with no and/or operators
return SingleCheckForNotCarried(m, objectivestr);
}
// test each half independently and combine the results
bool first = SingleCheckForNotCarried(m, arglist[0]);
// this will recursively parse the property test string with implicit nesting for multiple logical tests of the
// form A * B * C * D being grouped as A * (B * (C * D))
bool second = CheckForNotCarried(m, arglist[1]);
int andposition = objectivestr.IndexOf("&");
int orposition = objectivestr.IndexOf("|");
// for the & operator
// notrigger if
// notcarrying A | notcarrying B
// people will actually think of it as not(carrying A | carrying B)
// which is
// notrigger if
// notcarrying A && notcarrying B
// similarly for the & operator
// combine them based upon the operator
if (andposition > 0 && orposition <= 0 || andposition > 0 && andposition < orposition)
{
// and operator (see explanation above)
return first || second;
}
if (orposition > 0 && andposition <= 0 || orposition > 0 && orposition < andposition)
{
// or operator (see explanation above)
return first && second;
}
// should never get here
return false;
}
public static bool SingleCheckForNotCarried(Mobile m, string objectivestr)
{
if (m == null || objectivestr == null)
{
return true;
}
bool has_no_such_item = true;
// check to see whether there is an objective specification as well. The format is name[,type][,EQUIPPED][,objective,objective,...]
string[] objstr = ParseString(objectivestr, 8, ",");
string itemname = objstr[0];
// check for attachment keyword
if (itemname == "ATTACHMENT")
{
// syntax is ATTACHMENT,name,type
if (objstr.Length > 1)
{
string aname = objstr[1];
Type atype = null;
if (objstr.Length > 2)
{
try
{
atype = AssemblyHandler.FindTypeByName(objstr[2]);
}
catch { }
}
return true;
}
return true;
}
bool equippedonly = false;
string typestr = null;
int objoffset = 1;
// is there a type specification?
while (objoffset < objstr.Length)
{
if (objstr[objoffset] != null && objstr[objoffset].Length > 0)
{
char startc = objstr[objoffset][0];
if (startc >= '0' && startc <= '9')
{
// this is the start of the numeric objective specifications
break;
}
if (objstr[objoffset] == "EQUIPPED")
{
equippedonly = true;
}
else
{
// treat as a type specification if it does not begin with a numeric char
// and is not the EQUIPPED keyword
typestr = objstr[objoffset];
}
}
objoffset++;
}
// look for the item
Item testitem = SearchMobileForItem(m, itemname, typestr, false, equippedonly);
// found the item
if (testitem != null)
{
// is the equippedonly flag set? If so then see if the item is equipped
if (equippedonly && testitem.Parent == m || !equippedonly)
{
has_no_such_item = false;
}
}
return has_no_such_item;
}
public static Item FindItemByName(XmlSpawner fromspawner, string name, string typestr)
{
if (name == null)
{
return null;
}
int count = 0;
Item founditem = FindInRecentItemSearchList(fromspawner, name, typestr);
if (founditem != null)
{
return founditem;
}
Type targettype = null;
if (typestr != null)
{
targettype = AssemblyHandler.FindTypeByName(typestr);
}
// search through all items in the world and find the first one with a matching name
foreach (Item item in World.Items.Values)
{
Type itemtype = item.GetType();
if (!item.Deleted && (name.Length == 0 || string.Compare(item.Name, name, true) == 0))
{
if (typestr == null ||
targettype != null && (itemtype.Equals(targettype) || itemtype.IsSubclassOf(targettype)))
{
founditem = item;
count++;
// added the break in to return the first match instead of forcing uniqueness (overrides the count test)
break;
}
}
}
if (count == 1) // if a unique item is found then success
{
// add this to the recent search list
AddToRecentItemSearchList(fromspawner, founditem);
return founditem;
}
return null;
}
public static Mobile FindMobileByName(XmlSpawner fromspawner, string name, string typestr)
{
if (name == null)
{
return null;
}
int count = 0;
Mobile foundmobile = FindInRecentMobileSearchList(fromspawner, name, typestr);
if (foundmobile != null)
{
return foundmobile;
}
Type targettype = null;
if (typestr != null)
{
targettype = AssemblyHandler.FindTypeByName(typestr);
}
// search through all mobiles in the world and find one with a matching name
foreach (Mobile mobile in World.Mobiles.Values)
{
Type mobtype = mobile.GetType();
if (!mobile.Deleted && (name.Length == 0 || string.Compare(mobile.Name, name, true) == 0) && (typestr == null ||
targettype != null && (mobtype.Equals(targettype) || mobtype.IsSubclassOf(targettype))))
{
foundmobile = mobile;
count++;
// added the break in to return the first match instead of forcing uniqueness (overrides the count test)
break;
}
}
// if a unique item is found then success
if (count == 1)
{
// add this to the recent search list
AddToRecentMobileSearchList(fromspawner, foundmobile);
return foundmobile;
}
return null;
}
public static XmlSpawner FindSpawnerByName(XmlSpawner fromspawner, string name)
{
if (name == null)
{
return null;
}
if (name.StartsWith("0x"))
{
uint serial;
try
{
serial = Convert.ToUInt32(name, 16);
return World.FindEntity<XmlSpawner>((Serial)serial);
}
catch { }
}
// do a quick search through the recent search list to see if it is there
XmlSpawner foundspawner = FindInRecentSpawnerSearchList(fromspawner, name);
if (foundspawner != null)
{
return foundspawner;
}
int count = 0;
// search through all xmlspawners in the world and find one with a matching name
foreach (Item item in World.Items.Values)
{
if (item is XmlSpawner spawner)
{
if (!spawner.Deleted && string.Compare(spawner.Name, name, true) == 0)
{
foundspawner = spawner;
count++;
// added the break in to return the first match instead of forcing uniqueness (overrides the count test)
break;
}
}
}
// if a unique item is found then success
if (count == 1)
{
// add this to the recent search list
AddToRecentSpawnerSearchList(fromspawner, foundspawner);
return foundspawner;
}
return null;
}
public static void AddToRecentSpawnerSearchList(XmlSpawner spawner, XmlSpawner target)
{
if (spawner == null || target == null)
{
return;
}
if (spawner.RecentSpawnerSearchList == null)
{
spawner.RecentSpawnerSearchList = new List<XmlSpawner>();
}
spawner.RecentSpawnerSearchList.Add(target);
// check the length and truncate if it gets too long
if (spawner.RecentSpawnerSearchList.Count > 100)
{
spawner.RecentSpawnerSearchList.RemoveAt(0);
}
}
public static XmlSpawner FindInRecentSpawnerSearchList(XmlSpawner spawner, string name)
{
if (spawner == null || name == null || spawner.RecentSpawnerSearchList == null)
{
return null;
}
List<XmlSpawner> deletelist = null;
XmlSpawner foundspawner = null;
foreach (XmlSpawner s in spawner.RecentSpawnerSearchList)
{
if (s.Deleted)
{
// clean it up
if (deletelist == null)
{
deletelist = new List<XmlSpawner>();
}
deletelist.Add(s);
}
else
if (string.Compare(s.Name, name, true) == 0)
{
foundspawner = s;
break;
}
}
if (deletelist != null)
{
foreach (XmlSpawner i in deletelist)
{
spawner.RecentSpawnerSearchList.Remove(i);
}
}
return foundspawner;
}
public static void AddToRecentItemSearchList(XmlSpawner spawner, Item target)
{
if (spawner == null || target == null)
{
return;
}
if (spawner.RecentItemSearchList == null)
{
spawner.RecentItemSearchList = new List<Item>();
}
spawner.RecentItemSearchList.Add(target);
// check the length and truncate if it gets too long
if (spawner.RecentItemSearchList.Count > 100)
{
spawner.RecentItemSearchList.RemoveAt(0);
}
}
public static Item FindInRecentItemSearchList(XmlSpawner spawner, string name, string typestr)
{
if (spawner == null || name == null || spawner.RecentItemSearchList == null)
{
return null;
}
List<Item> deletelist = null;
Item founditem = null;
Type targettype = null;
if (typestr != null)
{
targettype = AssemblyHandler.FindTypeByName(typestr);
}
foreach (Item item in spawner.RecentItemSearchList)
{
if (item.Deleted)
{
// clean it up
if (deletelist == null)
{
deletelist = new List<Item>();
}
deletelist.Add(item);
}
else
if (name.Length == 0 || string.Compare(item.Name, name, true) == 0)
{
if (typestr == null ||
targettype != null && (item.GetType().Equals(targettype) || item.GetType().IsSubclassOf(targettype)))
{
founditem = item;
break;
}
}
}
if (deletelist != null)
{
foreach (Item i in deletelist)
{
spawner.RecentItemSearchList.Remove(i);
}
}
return founditem;
}
public static void AddToRecentMobileSearchList(XmlSpawner spawner, Mobile target)
{
if (spawner == null || target == null)
{
return;
}
if (spawner.RecentMobileSearchList == null)
{
spawner.RecentMobileSearchList = new List<Mobile>();
}
spawner.RecentMobileSearchList.Add(target);
// check the length and truncate if it gets too long
if (spawner.RecentMobileSearchList.Count > 100)
{
spawner.RecentMobileSearchList.RemoveAt(0);
}
}
public static Mobile FindInRecentMobileSearchList(XmlSpawner spawner, string name, string typestr)
{
if (spawner == null || name == null || spawner.RecentMobileSearchList == null)
{
return null;
}
List<Mobile> deletelist = null;
Mobile foundmobile = null;
Type targettype = null;
if (typestr != null)
{
targettype = AssemblyHandler.FindTypeByName(typestr);
}
foreach (Mobile m in spawner.RecentMobileSearchList)
{
if (m.Deleted)
{
// clean it up
if (deletelist == null)
{
deletelist = new List<Mobile>();
}
deletelist.Add(m);
}
else
if (name.Length == 0 || string.Compare(m.Name, name, true) == 0)
{
if (typestr == null ||
targettype != null && (m.GetType().Equals(targettype) || m.GetType().IsSubclassOf(targettype)))
{
foundmobile = m;
break;
}
}
}
if (deletelist != null)
{
foreach (Mobile i in deletelist)
{
spawner.RecentMobileSearchList.Remove(i);
}
}
return foundmobile;
}
public static string ApplySubstitution(XmlSpawner spawner, object o, string typeName)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
// go through the string looking for instances of {keyword}
string remaining = typeName;
while (!string.IsNullOrEmpty(remaining))
{
int startindex = remaining.IndexOf('{');
if (startindex == -1 || startindex + 1 >= remaining.Length)
{
// if there are no more delimiters then append the remainder and finish
sb.Append(remaining);
break;
}
// might be a substitution, check for keywords
int endindex = remaining.Substring(startindex + 1).IndexOf("}");
// if the ending delimiter cannot be found then just append and finish
if (endindex == -1)
{
sb.Append(remaining);
break;
}
// get the string up to the delimiter
string firstpart = remaining.Substring(0, startindex);
sb.Append(firstpart);
string keypart = remaining.Substring(startindex + 1, endindex);
// try to evaluate and then substitute the arg
string value = ParseForKeywords(spawner, o, keypart.Trim(), true, out _);
// trim off the " from strings
if (value != null)
{
value = value.Trim('"');
}
// replace the parsed value for the keyword
sb.Append(value);
// continue processing the rest of the string
if (endindex + startindex + 2 >= remaining.Length)
{
break;
}
remaining = remaining.Substring(endindex + startindex + 2, remaining.Length - endindex - startindex - 2);
}
return sb.ToString();
}
public static string ParseObjectType(string str)
{
string[] arglist = ParseSlashArgs(str, 2);
if (arglist != null && arglist.Length > 0)
{
// parse out any arguments of the form typename,arg,arg,..
string[] typeargs = ParseCommaArgs(arglist[0], 2);
if (typeargs.Length > 1)
{
return typeargs[0];
}
return arglist[0];
}
return null;
}
public static string[] ParseObjectArgs(string str)
{
string[] arglist = ParseSlashArgs(str, 2);
if (arglist.Length > 0)
{
string itemtypestring = arglist[0];
// parse out any arguments of the form typename,arg,arg,..
// find the first arg if it is there
string[] typeargs = null;
int argstart = 0;
if (!string.IsNullOrEmpty(itemtypestring))
{
argstart = itemtypestring.IndexOf(",") + 1;
}
if (argstart > 1 && argstart < itemtypestring.Length)
{
typeargs = ParseCommaArgs(itemtypestring.Substring(argstart), 15);
}
return typeargs;
}
return null;
}
// take a string of the form str-opendelim-str-closedelim-str-closedelimstr
public static string[] ParseToMatchingParen(string str, char opendelim, char closedelim)
{
int nopen = 1;
int nclose = 0;
int splitpoint = str.Length;
for (int i = 0; i < str.Length; i++)
{
// walk through the string until a matching close delimstr is found
if (str[i] == opendelim)
{
nopen++;
}
if (str[i] == closedelim)
{
nclose++;
}
if (nopen == nclose)
{
splitpoint = i;
break;
}
}
string[] args = new string[2];
// allow missing closing delimiters at the end of the line, basically just treat eol as a closing delim
args[0] = str.Substring(0, splitpoint);
args[1] = "";
if (splitpoint + 1 < str.Length)
{
args[1] = str.Substring(splitpoint + 1, str.Length - splitpoint - 1);
}
return args;
}
public static string[] ParseString(string str, int nitems, string delimstr)
{
if (str == null || delimstr == null)
{
return null;
}
char[] delims = delimstr.ToCharArray();
str = str.Trim();
string[] args = str.Split(delims, nitems);
return args;
}
public static string[] ParseSlashArgs(string str, int nitems)
{
if (str == null)
{
return null;
}
str = str.Trim();
string[] args;
// this supports strings that may have special html formatting in them that use the /
if (str.IndexOf("</") >= 0 || str.IndexOf("/>") >= 0)
{
// or use indexof to do it with more context control
List<string> tmparray = new List<string>();
// find the next slash char
int index = 0;
int preindex = 0;
int searchindex = 0;
int length = str.Length;
while (index >= 0 && searchindex < length && tmparray.Count < nitems - 1)
{
index = str.IndexOf('/', searchindex);
if (index >= 0)
{
// check the char before it and after it to ignore </ and />
if (index > 0 && str[index - 1] == '<' || index < length - 1 && str[index + 1] == '>')
{
// skip it
searchindex = index + 1;
}
else
{
// split it
tmparray.Add(str.Substring(preindex, index - preindex));
preindex = index + 1;
searchindex = preindex;
}
}
}
// is there still room for more args?
if (tmparray.Count <= nitems - 1 && preindex < length)
{
// searched past the end and didnt find anything
tmparray.Add(str.Substring(preindex, length - preindex));
}
// turn tmparray into a string[]
args = new string[tmparray.Count];
tmparray.CopyTo(args);
}
else
{
// just use split to do it with no context control
args = str.Split(slashdelim, nitems);
}
return args;
}
public static string[] ParseCommaArgs(string str, int nitems)
{
if (str == null)
{
return null;
}
str = str.Trim();
string[] args = str.Split(commadelim, nitems);
return args;
}
public static string[] ParseLiteralTerminator(string str)
{
if (str == null)
{
return null;
}
str = str.Trim();
string[] args = str.Split(literalend, 2);
return args;
}
public static string[] ParseSemicolonArgs(string str, int nitems)
{
if (str == null)
{
return null;
}
str = str.Trim();
string[] args = str.Split(semicolondelim, nitems);
return args;
}
public static string[] SplitString(string str, string separator)
{
if (str == null || separator == null)
{
return null;
}
int lastindex = 0;
List<string> strargs = new List<string>();
while (true)
{
// go through the string and find the first instance of the separator
var index = str.IndexOf(separator);
if (index < 0)
{
// no separator so its the end of the string
strargs.Add(str);
break;
}
string arg = str.Substring(lastindex, index);
strargs.Add(arg);
str = str.Substring(index + separator.Length, str.Length - (index + separator.Length));
}
// now make the string args
string[] args = new string[strargs.Count];
for (int i = 0; i < strargs.Count; i++)
{
args[i] = strargs[i];
}
return args;
}
public static void AddSpawnItem(XmlSpawner spawner, object invoker, XmlSpawner.SpawnObject theSpawn, Item item, Point3D location, Map map, Mobile trigmob, bool requiresurface,
string propertyString, out string status_str)
{
AddSpawnItem(spawner, invoker, theSpawn, item, location, map, trigmob, requiresurface, null, propertyString, false, out status_str);
}
public static void AddSpawnItem(XmlSpawner spawner, XmlSpawner.SpawnObject theSpawn, Item item, Point3D location, Map map, Mobile trigmob, bool requiresurface,
List<XmlSpawner.SpawnPositionInfo> spawnpositioning, string propertyString, bool smartspawn, out string status_str)
{
AddSpawnItem(spawner, spawner, theSpawn, item, location, map, trigmob, requiresurface, spawnpositioning, propertyString, smartspawn, out status_str);
}
public static void AddSpawnItem(XmlSpawner spawner, object invoker, XmlSpawner.SpawnObject theSpawn, Item item, Point3D location, Map map, Mobile trigmob, bool requiresurface,
List<XmlSpawner.SpawnPositionInfo> spawnpositioning, string propertyString, bool smartspawn, out string status_str)
{
status_str = null;
if (item == null || theSpawn == null)
{
return;
}
// add the item to the spawned list
theSpawn.SpawnedObjects.Add(item);
item.Spawner = spawner;
if (spawner != null)
{
// this is being called by a spawner so use spawner information for placement
if (!spawner.Deleted)
{
// set the item amount
if (spawner.StackAmount > 1 && item.Stackable)
{
item.Amount = spawner.StackAmount;
}
// if this is in any container such as a pack then add to the container.
if (spawner.Parent is Container parent)
{
Point3D loc = spawner.Location;
if (!smartspawn)
{
item.OnBeforeSpawn(loc, map);
}
item.Location = loc;
// check to see whether we drop or add the item based on the spawnrange
// this will distribute multiple items around the spawn point, and allow precise
// placement of single spawns at the spawn point
if (spawner.SpawnRange > 0)
{
parent.DropItem(item);
}
else
{
parent.AddItem(item);
}
}
else
{
// if the spawn entry is in a subgroup and has a packrange, then get the packcoord
Point3D packcoord = Point3D.Zero;
if (theSpawn.PackRange >= 0 && theSpawn.SubGroup > 0)
{
packcoord = spawner.GetPackCoord(theSpawn.SubGroup);
}
Point3D loc = spawner.GetSpawnPosition(requiresurface, theSpawn.PackRange, packcoord, spawnpositioning);
if (!smartspawn)
{
item.OnBeforeSpawn(loc, map);
}
// standard placement for all items in the world
item.MoveToWorld(loc, map);
}
}
else
{
// if the spawner has already been deleted then delete the item since it cannot be cleaned up by spawner deletion any longer
item.Delete();
return;
}
}
else
{
if (!smartspawn)
{
item.OnBeforeSpawn(location, map);
}
// use the location and map info passed in
// this allows AddSpawnItem to be called by objects other than spawners as long as they pass in a valid SpawnObject
item.MoveToWorld(location, map);
}
// clear the taken flag on all newly spawned items
ItemFlags.SetTaken(item, false);
if (!smartspawn)
{
item.OnAfterSpawn();
}
// apply the parsed arguments from the typestring using setcommand
// be sure to do this after setting map and location so that errors dont place the mob on the internal map
ApplyObjectStringProperties(spawner, propertyString, item, trigmob, spawner, out status_str);
}
public static bool SpawnTypeKeyword(object invoker, XmlSpawner.SpawnObject TheSpawn, string typeName, string substitutedtypeName,
Mobile triggermob, Map map, out string status_str) =>
SpawnTypeKeyword(invoker, TheSpawn, typeName, substitutedtypeName,
triggermob, map, out status_str, 0);
public static bool SpawnTypeKeyword(object invoker, XmlSpawner.SpawnObject TheSpawn, string typeName, string substitutedtypeName, Mobile triggermob, Map map, out string status_str, byte loops)
{
status_str = null;
if (typeName == null || TheSpawn == null || substitutedtypeName == null)
{
return false;
}
XmlSpawner spawner = invoker as XmlSpawner;
// check for any special keywords that might appear in the type such as SET, GIVE, or TAKE
if (IsTypeKeyword(typeName))
{
typeKeyword kw = typeKeywordHash[typeName];
switch (kw)
{
case typeKeyword.SET:
{
// the syntax is SET/prop/value/prop2/value...
// check for the SET,itemname or serialno[,itemtype]/prop/value form is used
string[] arglist = ParseSlashArgs(substitutedtypeName, 3);
string[] keywordargs = ParseString(arglist[0], 3, ",");
if (keywordargs.Length > 1)
{
string typestr = null;
if (keywordargs.Length > 2)
{
typestr = keywordargs[2];
}
// is the itemname a serialno?
object setitem = null;
if (keywordargs[1].StartsWith("0x"))
{
uint serial;
try
{
serial = Convert.ToUInt32(keywordargs[1], 16);
setitem = World.FindEntity((Serial)serial);
}
catch { }
}
else
{
// just look it up by name
setitem = FindItemByName(spawner, keywordargs[1], typestr);
}
if (setitem == null)
{
status_str = $"cant find unique item :{keywordargs[1]}";
return false;
}
ApplyObjectStringProperties(spawner, substitutedtypeName, setitem, triggermob, invoker, out status_str);
}
else if (spawner != null)
{
ApplyObjectStringProperties(spawner, substitutedtypeName, spawner.SetItem, triggermob, invoker, out status_str);
}
TheSpawn.SpawnedObjects.Add(new KeywordTag(substitutedtypeName, spawner));
break;
}
case typeKeyword.DESPAWN:
{
// the syntax is DESPAWN[,spawnername],subgroup
// first find the spawner and group
int subgroup = -1;
string[] arglist = ParseSlashArgs(substitutedtypeName, 3);
XmlSpawner targetspawner = spawner;
if (arglist.Length > 0)
{
string[] keywordargs = ParseString(arglist[0], 3, ",");
if (keywordargs.Length < 2)
{
status_str = "missing subgroup in DESPAWN";
return false;
}
string subgroupstr = keywordargs[1];
string spawnerstr = null;
if (keywordargs.Length > 2)
{
spawnerstr = keywordargs[1];
subgroupstr = keywordargs[2];
}
if (spawnerstr != null)
{
targetspawner = FindSpawnerByName(spawner, spawnerstr);
}
if (!int.TryParse(subgroupstr, out subgroup))
{
subgroup = -1;
}
}
if (subgroup == -1)
{
status_str = "invalid subgroup in DESPAWN";
return false;
}
if (targetspawner != null)
{
targetspawner.ClearSubgroup(subgroup);
}
else
{
status_str = "invalid spawner in DESPAWN";
return false;
}
TheSpawn.SpawnedObjects.Add(new KeywordTag(substitutedtypeName, spawner));
break;
}
case typeKeyword.SPAWN:
{
// the syntax is SPAWN[,spawnername],subgroup
// first find the spawner and group
int subgroup = -1;
string[] arglist = ParseSlashArgs(substitutedtypeName, 3);
XmlSpawner targetspawner = spawner;
if (arglist.Length > 0)
{
string[] keywordargs = ParseString(arglist[0], 3, ",");
if (keywordargs.Length < 2)
{
status_str = "missing subgroup in SPAWN";
return false;
}
string subgroupstr = keywordargs[1];
string spawnerstr = null;
if (keywordargs.Length > 2)
{
spawnerstr = keywordargs[1];
subgroupstr = keywordargs[2];
}
if (spawnerstr != null)
{
targetspawner = FindSpawnerByName(spawner, spawnerstr);
}
if (!int.TryParse(subgroupstr, out subgroup))
{
subgroup = -1;
}
}
if (subgroup == -1)
{
status_str = "invalid subgroup in SPAWN";
return false;
}
if (targetspawner != null)
{
if (spawner != targetspawner)
{
// allow spawning of other spawners to be forced and ignore the normal loop protection
if (loops >= XmlSpawner.MaxLoops) //preventing looping from spawner to spawner, via recursive linked method calls
{
status_str = "recursive looping stop in SPAWN";
return false;
}
targetspawner.SpawnSubGroup(subgroup, false, true, (byte)(loops + 1));
}
else
{
if (loops >= XmlSpawner.MaxLoops)
{
status_str = "recursive looping stop in SPAWN";
return false;
}
targetspawner.SpawnSubGroup(subgroup, (byte)(loops + 1));
}
}
else
{
status_str = "invalid spawner in SPAWN";
return false;
}
TheSpawn.SpawnedObjects.Add(new KeywordTag(substitutedtypeName, spawner));
break;
}
case typeKeyword.GOTO:
{
// the syntax is GOTO/subgroup
string[] arglist = ParseSlashArgs(substitutedtypeName, 3);
int group = -1;
if (arglist.Length < 2)
{
status_str = "insufficient args to GOTO";
}
else
{
if (!int.TryParse(arglist[1], out group))
{
status_str = "invalid subgroup arg to GOTO";
group = -1;
}
}
if (status_str != null)
{
return false;
}
// move the sequence to the specified subgroup
if (group >= 0 && spawner != null && !spawner.Deleted)
{
// note, this will activate sequential spawning if it wasnt already set
spawner.SequentialSpawn = group;
// and suppress sequential advancement so that the specified group is the next to spawn
spawner.HoldSequence = true;
}
TheSpawn.SpawnedObjects.Add(new KeywordTag(substitutedtypeName, spawner, 2));
break;
}
case typeKeyword.COMMAND:
{
// the syntax is COMMAND/commandstring
string[] arglist = ParseSlashArgs(substitutedtypeName, 3);
if (arglist.Length > 0)
{
// mod to use a dummy char to issue commands
if (CommandMobileName != null)
{
Mobile dummy = FindMobileByName(spawner, CommandMobileName, "Mobile");
if (dummy != null)
{
CommandSystem.Handle(dummy, $"{CommandSystem.Prefix}{arglist[1]}");
}
}
else
if (triggermob != null && !triggermob.Deleted)
{
CommandSystem.Handle(triggermob, $"{CommandSystem.Prefix}{arglist[1]}");
}
}
else
{
status_str = "insufficient args to COMMAND";
}
TheSpawn.SpawnedObjects.Add(new KeywordTag(substitutedtypeName, spawner));
break;
}
default:
{
status_str = "unrecognized keyword";
// should never get here
break;
}
}
// indicate successful keyword spawn
return true;
}
// should never get here
status_str = "unrecognized keyword";
return false;
}
public static List<Item> GetItems(Region r)
{
List<Item> list = new List<Item>();
if (r == null)
{
return list;
}
Sector[] sectors = r.Sectors;
if (sectors != null)
{
for (int i = 0; i < sectors.Length; i++)
{
Sector sector = sectors[i];
foreach (Item item in sector.Items)
{
if (Region.Find(item.Location, item.Map).IsPartOf(r))
{
list.Add(item);
}
}
}
}
return list;
}
}