diff --git a/Projects/UOContent/Engines/XMLSpawner/BaseXmlSpawner.cs b/Projects/UOContent/Engines/XMLSpawner/BaseXmlSpawner.cs new file mode 100644 index 000000000..9866f8f57 --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/BaseXmlSpawner.cs @@ -0,0 +1,3982 @@ +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 plist = new(); // hold propertyinfo list + public Type t; + } + + private static readonly Type typeofTimeSpan = typeof(TimeSpan); + private static readonly Type typeofParsable = typeof(ParsableAttribute); + private static readonly Type typeofCustomEnum = typeof(CustomEnumAttribute); + + private static bool IsParsable(Type t) => t == typeofTimeSpan || t.IsDefined(typeofParsable, false); + + 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 typeKeywordHash = new(); + private static readonly Dictionary typemodKeywordHash = new(); + private static readonly Dictionary valueKeywordHash = new(); + private static readonly Dictionary 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 = DateTime.UtcNow + 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(); + } + // 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 = DateTime.UtcNow + 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 - DateTime.UtcNow); + writer.Write(m_Delay); + writer.Write(m_Condition); + writer.Write(m_Goto); + writer.Write(m_TimeoutEnd - DateTime.UtcNow); + 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(); + 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 = DateTime.UtcNow + timeoutdelay; + m_Timeout = reader.ReadTimeSpan(); + m_TrigMob = reader.ReadEntity(); + + 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 + string status_str; + + if (TestItemProperty(m_Spawner, m_Spawner, m_Condition, out status_str)) + { + // 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 < DateTime.UtcNow) + { + // 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)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 (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. 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)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)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)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)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 /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(); + } + + 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, + // handle value keywords that may take comma args + + // itemarglist[1] will contain arg2/arg3/arg4>/arg5 + // additemstr should have the full list of args /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 0 && testString[0] == '~') + { + invertreturn = true; + testString = testString.Substring(1, testString.Length - 1); + } + + string[] arglist = ParseString(testString, 2, "=> 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 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 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 { } + } + // try to find the attachment on the mob + if (XmlAttach.FindAttachmentOnMobile(m, atype, aname) != null) + { + return true; + } + + 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) + { + // check to see if it is a quest token item. If so, then check validity, otherwise just finding it is enough + if (testitem is IXmlQuest token) + { + if (token.IsValid) + { + if (objstr.Length > objoffset) + { + has_valid_item = true; + // get any objectives and test for them. If any of the required conditions are false, then dont trigger + for (int n = objoffset; n < objstr.Length; n++) + { + try + { + switch (int.Parse(objstr[n]) - objoffset + 1) + { + case 1: + { + if (!token.Completed1) + { + has_valid_item = false; + } + + break; + } + case 2: + { + if (!token.Completed2) + { + has_valid_item = false; + } + + break; + } + case 3: + { + if (!token.Completed3) + { + has_valid_item = false; + } + + break; + } + case 4: + { + if (!token.Completed4) + { + has_valid_item = false; + } + + break; + } + case 5: + { + if (!token.Completed5) + { + has_valid_item = false; + } + + break; + } + } + } + catch { } + } + } + else + // if an objective list has not been specified then just a valid item is enough + { + has_valid_item = true; + } + } + } + else + { + // 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 { } + } + + // try to find the attachment on the mob + if (XmlAttach.FindAttachmentOnMobile(m, atype, aname) != null) + { + return false; + } + + 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) + { + // check to see if it is a quest token item. If so, then check validity, otherwise just finding it is enough + if (testitem is IXmlQuest token && token.IsValid) + { + if (objstr.Length > objoffset) + { + has_no_such_item = true; + // get any objectives and test for them. If any of the required conditions are true, then block trigger + for (int n = objoffset; n < objstr.Length; n++) + { + try + { + switch (int.Parse(objstr[n]) - objoffset + 1) + { + case 1: + { + if (token.Completed1) + { + has_no_such_item = false; + } + + break; + } + case 2: + { + if (token.Completed2) + { + has_no_such_item = false; + } + + break; + } + case 3: + { + if (token.Completed3) + { + has_no_such_item = false; + } + + break; + } + case 4: + { + if (token.Completed4) + { + has_no_such_item = false; + } + + break; + } + case 5: + { + if (token.Completed5) + { + has_no_such_item = false; + } + + break; + } + } + } + catch { } + } + } + else + { + has_no_such_item = false; + } + } + else + { + // 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((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(); + } + 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 deletelist = null; + XmlSpawner foundspawner = null; + + foreach (XmlSpawner s in spawner.RecentSpawnerSearchList) + { + if (s.Deleted) + { + // clean it up + if (deletelist == null) + { + deletelist = new List(); + } + + 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(); + } + + 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 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(); + } + + 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(); + } + + 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 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(); + } + + 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 + Type ptype; + + string value = ParseForKeywords(spawner, o, keypart.Trim(), true, out ptype); + + // 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 tmparray = new List(); + // 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 + 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 strargs = new List(); + 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 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 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 GetItems(Region r) + { + List list = new List(); + 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; + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/ExceptionLogging.cs b/Projects/UOContent/Engines/XMLSpawner/ExceptionLogging.cs new file mode 100644 index 000000000..a60a4017d --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/ExceptionLogging.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; + +namespace Server.Diagnostics; + +// TODO: Replace this with serilog +public class ExceptionLogging +{ + public static string LogDirectory { get; set; } + + private static StreamWriter _Output; + + public static StreamWriter Output + { + get + { + if (_Output == null) + { + _Output = new StreamWriter(Path.Combine(LogDirectory, $"{Core.Now.ToLongDateString()}.log"), true) + { + AutoFlush = true + }; + + _Output.WriteLine("##############################"); + _Output.WriteLine("Exception log started on {0}", Core.Now); + _Output.WriteLine(); + } + + return _Output; + } + } + + static ExceptionLogging() + { + var directory = Path.Combine(Core.BaseDirectory, "Logs/Exceptions"); + + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + LogDirectory = directory; + } + + public static void LogException(Exception e) + { + Utility.PushColor(ConsoleColor.Red); + Console.WriteLine("Caught Exception:"); + Utility.PopColor(); + + Utility.PushColor(ConsoleColor.DarkRed); + Console.WriteLine(e); + Utility.PopColor(); + + Output.WriteLine("Exception Caught: {0}", Core.Now); + Output.WriteLine(e); + Output.WriteLine(); + } + + public static void LogException(Exception e, string arg) + { + Utility.PushColor(ConsoleColor.Red); + Console.WriteLine("Caught Exception: {0}", arg); + Utility.PopColor(); + + Utility.PushColor(ConsoleColor.DarkRed); + Console.WriteLine(e); + Utility.PopColor(); + + Output.WriteLine("Exception Caught: {0}", Core.Now); + Output.WriteLine(e); + Output.WriteLine(); + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/ItemFlags.cs b/Projects/UOContent/Engines/XMLSpawner/ItemFlags.cs new file mode 100644 index 000000000..ce893bfcf --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/ItemFlags.cs @@ -0,0 +1,134 @@ +using System; +using Server.Targeting; + +namespace Server.Items; + +public partial class ItemFlags +{ + private const int StealableFlag = 0x00200000; + private const int TakenFlag = 0x00100000; + + public static void SetStealable(Item target, bool value) + { + target?.SetSavedFlag(StealableFlag, value); + } + public static bool GetStealable(Item target) => target != null && target.GetSavedFlag(StealableFlag); + + public static void SetTaken(Item target, bool value) + { + target?.SetSavedFlag(TakenFlag, value); + } + public static bool GetTaken(Item target) => target != null && target.GetSavedFlag(TakenFlag); + + [Usage("Flag flagfield")] + [Description("Gets the state of the specified SavedFlag on any item")] + public static void GetFlag_OnCommand(CommandEventArgs e) + { + int flag=0; + bool error = false; + if (e.Arguments.Length > 0) + { + if (e.Arguments[0].StartsWith("0x")) + { + try{flag = Convert.ToInt32(e.Arguments[0].Substring(2), 16); } catch { error = true;} + } else + { + try{flag = int.Parse(e.Arguments[0]); } catch { error = true;} + } + + } + if (!error) + { + e.Mobile.Target = new GetFlagTarget(e,flag); + } else + { + try{ + e.Mobile.SendMessage(33,"Flag: Bad flagfield argument"); + } catch {} + } + } + + private class GetFlagTarget : Target + { + private CommandEventArgs m_e; + private int m_flag; + + public GetFlagTarget(CommandEventArgs e, int flag) : base (30, false, TargetFlags.None) + { + m_e = e; + m_flag = flag; + } + protected override void OnTarget(Mobile from, object targeted) + { + if (targeted is Item item) + { + bool state = item.GetSavedFlag(m_flag); + + from.SendMessage("Flag (0x{0:X}) = {1}",m_flag,state); + } else + { + from.SendMessage("Must target an Item"); + } + } + } + + + [Usage("Stealable [true/false]")] + [Description("Sets/gets the stealable flag on any item")] + public static void SetStealable_OnCommand(CommandEventArgs e) + { + bool state = false; + bool error = false; + if (e.Arguments.Length > 0) + { + try + { + state = bool.Parse(e.Arguments[0]); + } + catch + { + error = true; + } + } + if (!error) + { + e.Mobile.Target = new SetStealableTarget(e, state); + } + + } + + private class SetStealableTarget : Target + { + private CommandEventArgs m_e; + private bool m_state; + private bool set; + + public SetStealableTarget(CommandEventArgs e, bool state) : base (30, false, TargetFlags.None) + { + m_e = e; + m_state = state; + if (e.Arguments.Length > 0) + { + set = true; + } + } + protected override void OnTarget(Mobile from, object targeted) + { + if (targeted is Item item) + { + if (set) + { + SetStealable(item, m_state); + } + + bool state = GetStealable(item); + + from.SendMessage("Stealable = {0}",state); + + } else + { + from.SendMessage("Must target an Item"); + } + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/SpawnerExporter.cs b/Projects/UOContent/Engines/XMLSpawner/SpawnerExporter.cs new file mode 100644 index 000000000..d5fa8d955 --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/SpawnerExporter.cs @@ -0,0 +1,283 @@ +using Server.Commands.Generic; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using Server.Engines.Spawners; + +/* +** Sno's distro spawner importer/exporter +** +** [exportspawner filename - Saves distro spawners to XML to 'Saves/Spawners/filename' +** +** [importspawner filename - Restores distro spawners from 'Saves/Spawners/filename'. Note, this command does not check for +** duplication, so if you run it more than once you will end up with multiple spawners. +** +** These spawns can also be imported back in as XmlSpawners by using the '[xmlimportspawners Saves/Spawners/filename' command. +*/ + +namespace Server.Mobiles; + +public class SpawnerExporter +{ + public static void Initialize() + { + TargetCommands.Register(new ExportSpawnerCommand()); + CommandSystem.Register("ImportSpawners", AccessLevel.Administrator, ImportSpawners_OnCommand); + } + + public class ExportSpawnerCommand : BaseCommand + { + public ExportSpawnerCommand() + { + AccessLevel = AccessLevel.Administrator; + Supports = CommandSupport.Area | CommandSupport.Region | CommandSupport.Global | CommandSupport.Multi | CommandSupport.Single; + Commands = new[] { "ExportSpawner" }; + ObjectTypes = ObjectTypes.Items; + Usage = "ExportSpawner "; + Description = "Exports all Spawner objects to the specified filename."; + ListOptimized = true; + } + + public override void ExecuteList(CommandEventArgs e, List list) + { + string filename = e.GetString(0); + + ArrayList spawners = new ArrayList(); + + for (int i = 0; i < list.Count; ++i) + { + if (list[i] is Spawner) + { + Spawner spawner = (Spawner)list[i]; + if (!spawner.Deleted && spawner.Map != Map.Internal && spawner.Parent == null) + { + spawners.Add(spawner); + } + } + } + + AddResponse($"{spawners.Count.ToString()} spawners exported to Saves/Spawners/{filename}."); + + ExportSpawners(spawners, filename); + } + + public override bool ValidateArgs(BaseCommandImplementor impl, CommandEventArgs e) + { + if (e.Arguments.Length >= 1) + { + return true; + } + + e.Mobile.SendMessage($"Usage: {Usage}"); + return false; + } + + private void ExportSpawners(ArrayList spawners, string filename) + { + if (spawners.Count == 0) + { + return; + } + + if (!Directory.Exists("Saves/Spawners")) + { + Directory.CreateDirectory("Saves/Spawners"); + } + + string filePath = Path.Combine("Saves/Spawners", filename); + + using (StreamWriter op = new StreamWriter(filePath)) + { + XmlTextWriter xml = new XmlTextWriter(op) + { + Formatting = Formatting.Indented, + IndentChar = '\t', + Indentation = 1 + }; + + xml.WriteStartDocument(true); + + xml.WriteStartElement("spawners"); + + xml.WriteAttributeString("count", spawners.Count.ToString()); + + foreach (Spawner spawner in spawners) + { + ExportSpawner(spawner, xml); + } + + xml.WriteEndElement(); + + xml.Close(); + } + } + + private void ExportSpawner(Spawner spawner, XmlWriter xml) + { + xml.WriteStartElement("spawner"); + + xml.WriteStartElement("count"); + xml.WriteString(spawner.Count.ToString()); + xml.WriteEndElement(); + + xml.WriteStartElement("group"); + xml.WriteString(spawner.Group.ToString()); + xml.WriteEndElement(); + + xml.WriteStartElement("homerange"); + xml.WriteString(spawner.HomeRange.ToString()); + xml.WriteEndElement(); + + xml.WriteStartElement("walkingrange"); + xml.WriteString(spawner.WalkingRange.ToString()); + xml.WriteEndElement(); + + xml.WriteStartElement("maxdelay"); + xml.WriteString(spawner.MaxDelay.ToString()); + xml.WriteEndElement(); + + xml.WriteStartElement("mindelay"); + xml.WriteString(spawner.MinDelay.ToString()); + xml.WriteEndElement(); + + xml.WriteStartElement("team"); + xml.WriteString(spawner.Team.ToString()); + xml.WriteEndElement(); + + xml.WriteStartElement("creaturesname"); + foreach (var entry in spawner.Entries) + { + xml.WriteStartElement("creaturename"); + xml.WriteString(entry.SpawnedName); + xml.WriteEndElement(); + } + xml.WriteEndElement(); + + // Item properties + + xml.WriteStartElement("name"); + xml.WriteString(spawner.Name); + xml.WriteEndElement(); + + xml.WriteStartElement("location"); + xml.WriteString(spawner.Location.ToString()); + xml.WriteEndElement(); + + xml.WriteStartElement("map"); + xml.WriteString(spawner.Map.ToString()); + xml.WriteEndElement(); + + xml.WriteEndElement(); + } + } + + [Usage("ImportSpawners")] + [Description("Recreates Spawner items from the specified file.")] + public static void ImportSpawners_OnCommand(CommandEventArgs e) + { + if (e.Arguments.Length >= 1) + { + string filename = e.GetString(0); + string filePath = Path.Combine("Saves/Spawners", filename); + + if (File.Exists(filePath)) + { + XmlDocument doc = new XmlDocument(); + doc.Load(filePath); + + XmlElement root = doc["spawners"]; + + int successes = 0, failures = 0; + + foreach (XmlElement spawner in root.GetElementsByTagName("spawner")) + { + try + { + ImportSpawner(spawner); + successes++; + } + catch (Exception ex) + { + failures++; + Diagnostics.ExceptionLogging.LogException(ex); + } + } + + e.Mobile.SendMessage("{0} spawners loaded successfully from {1}, {2} failures.", successes, filePath, failures); + } + else + { + e.Mobile.SendMessage("File {0} does not exist.", filePath); + } + } + else + { + e.Mobile.SendMessage("Usage: [ImportSpawners "); + } + } + + private static string GetText(XmlNode node, string defaultValue) + { + if (node == null) + { + return defaultValue; + } + + return node.InnerText; + } + + private static void ImportSpawner(XmlNode node) + { + int count = int.Parse(GetText(node["count"], "1")); + int homeRange = int.Parse(GetText(node["homerange"], "4")); + + int walkingRange = int.Parse(GetText(node["walkingrange"], "-1")); + + int team = int.Parse(GetText(node["team"], "0")); + + bool group = bool.Parse(GetText(node["group"], "False")); + TimeSpan maxDelay = TimeSpan.Parse(GetText(node["maxdelay"], "10:00")); + TimeSpan minDelay = TimeSpan.Parse(GetText(node["mindelay"], "05:00")); + IEnumerable creaturesName = LoadCreaturesName(node["creaturesname"]); + + string name = GetText(node["name"], "Spawner"); + Point3D location = Point3D.Parse(GetText(node["location"], "Error")); + Map map = Map.Parse(GetText(node["map"], "Error")); + + Spawner spawner = new Spawner(count, minDelay, maxDelay, team, homeRange, creaturesName.ToArray()); + if (walkingRange >= 0) + { + spawner.WalkingRange = walkingRange; + } + + spawner.Name = name; + spawner.MoveToWorld(location, map); + if (spawner.Map == Map.Internal) + { + spawner.Delete(); + throw new Exception("Spawner created on Internal map."); + } + spawner.Respawn(); + } + + private static IEnumerable LoadCreaturesName(XmlElement node) + { + List names = new List(); + + if (node != null) + { + foreach (XmlElement ele in node.GetElementsByTagName("creaturename")) + { + if (ele != null) + { + names.Add(ele.InnerText); + } + } + } + + return names; + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlPropsGump.cs b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlPropsGump.cs new file mode 100644 index 000000000..29e451244 --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlPropsGump.cs @@ -0,0 +1,721 @@ +using Server.Commands.Generic; +using Server.Network; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using CPA = Server.CommandPropertyAttribute; +/* +** modified properties gumps taken from RC0 properties gump scripts to support the special XmlSpawner properties gump +*/ + +namespace Server.Gumps; + +public class XmlPropertiesGump : Gump +{ + private readonly ArrayList m_List; + private int m_Page; + private readonly Mobile m_Mobile; + private readonly object m_Object; + private readonly Stack m_Stack; + + public static readonly bool OldStyle = PropsConfig.OldStyle; + + public static readonly int GumpOffsetX = PropsConfig.GumpOffsetX; + public static readonly int GumpOffsetY = PropsConfig.GumpOffsetY; + + public static readonly int TextHue = PropsConfig.TextHue; + public static readonly int TextOffsetX = PropsConfig.TextOffsetX; + + public static readonly int OffsetGumpID = PropsConfig.OffsetGumpID; + public static readonly int EntryGumpID = PropsConfig.EntryGumpID; + public static readonly int BackGumpID = PropsConfig.BackGumpID; + public static readonly int SetGumpID = PropsConfig.SetGumpID; + + public static readonly int SetWidth = PropsConfig.SetWidth; + public static readonly int SetOffsetX = PropsConfig.SetOffsetX, SetOffsetY = PropsConfig.SetOffsetY; + public static readonly int SetButtonID1 = PropsConfig.SetButtonID1; + public static readonly int SetButtonID2 = PropsConfig.SetButtonID2; + + public static readonly int OffsetSize = PropsConfig.OffsetSize; + + public static readonly int EntryHeight = PropsConfig.EntryHeight; + public static readonly int BorderSize = PropsConfig.BorderSize; + + private static readonly int NameWidth = 103; + private static readonly int ValueWidth = 82; + + private static readonly int EntryCount = 66; + private static readonly int ColumnEntryCount = 22; + + private static readonly int TypeWidth = NameWidth + OffsetSize + ValueWidth; + + private static readonly int TotalWidth = OffsetSize + NameWidth + OffsetSize + ValueWidth + OffsetSize + SetWidth + OffsetSize; + + public XmlPropertiesGump(Mobile mobile, object o) : base(GumpOffsetX, GumpOffsetY) + { + m_Mobile = mobile; + m_Object = o; + m_List = BuildList(); + + Initialize(0); + } + + public XmlPropertiesGump(Mobile mobile, object o, Stack stack, StackEntry parent) : base(GumpOffsetX, GumpOffsetY) + { + m_Mobile = mobile; + m_Object = o; + m_Stack = stack; + m_List = BuildList(); + + if (parent != null) + { + if (m_Stack == null) + { + m_Stack = new Stack(); + } + + m_Stack.Push(parent); + } + + Initialize(0); + } + + public XmlPropertiesGump(Mobile mobile, object o, Stack stack, ArrayList list, int page) : base(GumpOffsetX, GumpOffsetY) + { + m_Mobile = mobile; + m_Object = o; + m_List = list; + m_Stack = stack; + + Initialize(page); + } + + private void Initialize(int page) + { + m_Page = page; + + int count = m_List.Count - page * EntryCount; + + if (count < 0) + { + count = 0; + } + else if (count > EntryCount) + { + count = EntryCount; + } + + int lastIndex = page * EntryCount + count - 1; + + if (lastIndex >= 0 && lastIndex < m_List.Count && m_List[lastIndex] == null) + { + --count; + } + + int totalHeight = OffsetSize + (EntryHeight + OffsetSize) * (ColumnEntryCount + 1); + + AddPage(0); + + AddBackground(0, 0, TotalWidth * 3 + BorderSize * 2, BorderSize + totalHeight + BorderSize, BackGumpID); + AddImageTiled(BorderSize, BorderSize + EntryHeight, (TotalWidth - (OldStyle ? SetWidth + OffsetSize : 0)) * 3, totalHeight - EntryHeight, OffsetGumpID); + + int x = BorderSize + OffsetSize; + int y = BorderSize; + + if (m_Object is Item item) + { + AddLabelCropped(x + TextOffsetX, y, TypeWidth - TextOffsetX, EntryHeight, TextHue, item.Name); + } + + int propcount = 0; + for (int i = 0, index = page * EntryCount; i <= count && index < m_List.Count; ++i, ++index) + { + // do the multi column display + int column = propcount / ColumnEntryCount; + if (propcount % ColumnEntryCount == 0) + { + y = BorderSize; + } + + x = BorderSize + OffsetSize + column * (ValueWidth + NameWidth + OffsetSize * 2 + SetOffsetX + SetWidth); + y += EntryHeight + OffsetSize; + + object o = m_List[index]; + + if (o == null) + { + AddImageTiled(x - OffsetSize, y, TotalWidth, EntryHeight, BackGumpID + 4); + propcount++; + } + else if (o is PropertyInfo prop) + { + propcount++; + + // look for the default value of the equivalent property in the XmlSpawnerDefaults.DefaultEntry class + + int huemodifier = TextHue; + Mobiles.XmlSpawnerDefaults.DefaultEntry de = new Mobiles.XmlSpawnerDefaults.DefaultEntry(); + Type ftype = de.GetType(); + + var finfo = ftype.GetField(prop.Name); + + // is there an equivalent default field? + if (finfo != null) + { + // see if the value is different from the default + if (ValueToString(finfo.GetValue(de)) != ValueToString(prop)) + { + huemodifier = 68; + } + } + + AddImageTiled(x, y, NameWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, NameWidth - TextOffsetX, EntryHeight, huemodifier, prop.Name); + x += NameWidth + OffsetSize; + AddImageTiled(x, y, ValueWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, ValueWidth - TextOffsetX, EntryHeight, huemodifier, ValueToString(prop)); + x += ValueWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + CPA cpa = GetCPA(prop); + + if (prop.CanWrite && cpa != null && m_Mobile.AccessLevel >= cpa.WriteLevel) + { + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, i + 3); + } + } + } + } + + public static string[] m_BoolNames = { "True", "False" }; + public static object[] m_BoolValues = { true, false }; + + public static string[] m_PoisonNames = { "None", "Lesser", "Regular", "Greater", "Deadly", "Lethal" }; + public static object[] m_PoisonValues = { null, Poison.Lesser, Poison.Regular, Poison.Greater, Poison.Deadly, Poison.Lethal }; + + public override void OnResponse(NetState state, RelayInfo info) + { + Mobile from = state.Mobile; + + if (!BaseCommand.IsAccessible(from, m_Object)) + { + from.SendMessage("You may no longer access their properties."); + return; + } + + switch (info.ButtonID) + { + case 0: // Closed + { + if (m_Stack != null && m_Stack.Count > 0) + { + StackEntry entry = m_Stack.Pop(); + from.SendGump(new XmlPropertiesGump(from, entry.m_Object, m_Stack, null)); + } + + break; + } + case 1: // Previous + { + if (m_Page > 0) + { + from.SendGump(new XmlPropertiesGump(from, m_Object, m_Stack, m_List, m_Page - 1)); + } + + break; + } + case 2: // Next + { + if ((m_Page + 1) * EntryCount < m_List.Count) + { + from.SendGump(new XmlPropertiesGump(from, m_Object, m_Stack, m_List, m_Page + 1)); + } + + break; + } + default: + { + int index = m_Page * EntryCount + (info.ButtonID - 3); + + if (index >= 0 && index < m_List.Count) + { + PropertyInfo prop = m_List[index] as PropertyInfo; + + if (prop == null) + { + return; + } + + CPA attr = GetCPA(prop); + + if (!prop.CanWrite || attr == null || from.AccessLevel < attr.WriteLevel) + { + return; + } + + Type type = prop.PropertyType; + + if (IsType(type, typeofMobile) || IsType(type, typeofItem)) + { + from.SendGump(new XmlSetObjectGump(prop, from, m_Object, m_Stack, type, m_Page, m_List)); + } + else if (IsType(type, typeofType)) + { + from.Target = new XmlSetObjectTarget(prop, from, m_Object, m_Stack, type, m_Page, m_List); + } + else if (IsType(type, typeofPoint3D)) + { + from.SendGump(new XmlSetPoint3DGump(prop, from, m_Object, m_Stack, m_Page, m_List)); + } + else if (IsType(type, typeofPoint2D)) + { + from.SendGump(new XmlSetPoint2DGump(prop, from, m_Object, m_Stack, m_Page, m_List)); + } + else if (IsType(type, typeofTimeSpan)) + { + from.SendGump(new XmlSetTimeSpanGump(prop, from, m_Object, m_Stack, m_Page, m_List)); + } + else if (IsCustomEnum(type)) + { + from.SendGump(new XmlSetCustomEnumGump(prop, from, m_Object, m_Stack, m_Page, m_List, GetCustomEnumNames(type))); + } + else if (IsType(type, typeofEnum)) + { + from.SendGump(new XmlSetListOptionGump(prop, from, m_Object, m_Stack, m_Page, m_List, Enum.GetNames(type), GetObjects(Enum.GetValues(type)))); + } + else if (IsType(type, typeofBool)) + { + from.SendGump(new XmlSetListOptionGump(prop, from, m_Object, m_Stack, m_Page, m_List, m_BoolNames, m_BoolValues)); + } + else if (IsType(type, typeofString) || IsType(type, typeofReal) || IsType(type, typeofNumeric)) + { + from.SendGump(new XmlSetGump(prop, from, m_Object, m_Stack, m_Page, m_List)); + } + else if (IsType(type, typeofPoison)) + { + from.SendGump(new XmlSetListOptionGump(prop, from, m_Object, m_Stack, m_Page, m_List, m_PoisonNames, m_PoisonValues)); + } + else if (IsType(type, typeofMap)) + { + from.SendGump(new XmlSetListOptionGump(prop, from, m_Object, m_Stack, m_Page, m_List, Map.GetMapNames(), Map.GetMapValues())); + } + else if (IsType(type, typeofSkills) && m_Object is Mobile mobile) + { + from.SendGump(new XmlPropertiesGump(from, mobile, m_Stack, m_List, m_Page)); + from.SendGump(new SkillsGump(from, mobile)); + } + else if (HasAttribute(type, typeofPropertyObject, true)) + { + object obj = prop.GetValue(m_Object, null); + + from.SendGump(obj != null + ? new XmlPropertiesGump(from, obj, m_Stack, + new StackEntry(m_Object, prop)) + : new XmlPropertiesGump(from, m_Object, m_Stack, m_List, m_Page)); + } + } + + break; + } + } + } + + private static object[] GetObjects(Array a) + { + object[] list = new object[a.Length]; + + for (int i = 0; i < list.Length; ++i) + { + list[i] = a.GetValue(i); + } + + return list; + } + + private static bool IsCustomEnum(Type type) => type.IsDefined(typeofCustomEnum, false); + + private static string[] GetCustomEnumNames(Type type) + { + object[] attrs = type.GetCustomAttributes(typeofCustomEnum, false); + + if (attrs.Length == 0) + { + return new string[0]; + } + + CustomEnumAttribute ce = attrs[0] as CustomEnumAttribute; + + if (ce == null) + { + return new string[0]; + } + + return ce.Names; + } + + private static bool HasAttribute(Type type, Type check, bool inherit) + { + object[] objs = type.GetCustomAttributes(check, inherit); + + return objs.Length > 0; + } + + private static bool IsType(Type type, Type check) => type == check || type.IsSubclassOf(check); + + private static bool IsType(Type type, Type[] check) + { + for (int i = 0; i < check.Length; ++i) + { + if (IsType(type, check[i])) + { + return true; + } + } + + return false; + } + + private static readonly Type typeofMobile = typeof(Mobile); + private static readonly Type typeofItem = typeof(Item); + private static readonly Type typeofType = typeof(Type); + private static readonly Type typeofPoint3D = typeof(Point3D); + private static readonly Type typeofPoint2D = typeof(Point2D); + private static readonly Type typeofTimeSpan = typeof(TimeSpan); + private static readonly Type typeofCustomEnum = typeof(CustomEnumAttribute); + private static readonly Type typeofEnum = typeof(Enum); + private static readonly Type typeofBool = typeof(bool); + private static readonly Type typeofString = typeof(string); + private static readonly Type typeofPoison = typeof(Poison); + private static readonly Type typeofMap = typeof(Map); + private static readonly Type typeofSkills = typeof(Skills); + private static readonly Type typeofPropertyObject = typeof(PropertyObjectAttribute); + private static readonly Type typeofNoSort = typeof(NoSortAttribute); + + private static readonly Type[] typeofReal = + { + typeof(float), + typeof(double) + }; + + private static readonly Type[] typeofNumeric = + { + typeof(byte), + typeof(short), + typeof(int), + typeof(long), + typeof(sbyte), + typeof(ushort), + typeof(uint), + typeof(ulong) + }; + + private string ValueToString(PropertyInfo prop) => ValueToString(m_Object, prop); + + public static string ValueToString(object obj, PropertyInfo prop) + { + try + { + return ValueToString(prop.GetValue(obj, null)); + } + catch (Exception e) + { + return $"!{e.GetType()}!"; + } + } + + public static string ValueToString(object o) + { + if (o == null) + { + return "-null-"; + } + + if (o is string s1) + { + return $"\"{s1}\""; + } + + if (o is bool) + { + return o.ToString(); + } + + if (o is char c) + { + return $"0x{(int)c:X} '{c}'"; + } + + if (o is Serial s) + { + if (s.IsValid) + { + if (s.IsItem) + { + return $"(I) 0x{s.Value:X}"; + } + if (s.IsMobile) + { + return $"(M) 0x{s.Value:X}"; + } + } + + return $"(?) 0x{s.Value:X}"; + } + + if (o is byte || o is sbyte || o is short || o is ushort || o is int || o is uint || o is long || o is ulong) + { + return string.Format("{0} (0x{0:X})", o); + } + + if (o is double) + { + return o.ToString(); + } + + if (o is Mobile mobile) + { + return $"(M) 0x{mobile.Serial.Value:X} \"{mobile.Name}\""; + } + + if (o is Item item) + { + return $"(I) 0x{item.Serial:X}"; + } + + if (o is Type type) + { + return type.Name; + } + + return o.ToString(); + } + + private ArrayList BuildList() + { + Type type = m_Object.GetType(); + + PropertyInfo[] props = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public); + + ArrayList groups = GetGroups(type, props); + ArrayList list = new ArrayList(); + + for (int i = 0; i < groups.Count; ++i) + { + DictionaryEntry de = (DictionaryEntry)groups[i]; + ArrayList groupList = (ArrayList)de.Value; + + if (!HasAttribute((Type)de.Key, typeofNoSort, false)) + { + groupList.Sort(PropertySorter.Instance); + } + + if (i != 0) + { + list.Add(null); + } + + list.Add(de.Key); + list.AddRange(groupList); + } + + return list; + } + + private static readonly Type typeofCPA = typeof(CPA); + private static readonly Type typeofObject = typeof(object); + + private static CPA GetCPA(PropertyInfo prop) + { + object[] attrs = prop.GetCustomAttributes(typeofCPA, false); + + if (attrs.Length > 0) + { + return attrs[0] as CPA; + } + + return null; + } + + private ArrayList GetGroups(Type objectType, PropertyInfo[] props) + { + Hashtable groups = new Hashtable(); + + for (int i = 0; i < props.Length; ++i) + { + PropertyInfo prop = props[i]; + + if (prop.CanRead) + { + CPA attr = GetCPA(prop); + + if (attr != null && m_Mobile.AccessLevel >= attr.ReadLevel) + { + Type type = prop.DeclaringType; + + while (true) + { + Type baseType = type.BaseType; + + if (baseType == null || baseType == typeofObject) + { + break; + } + + if (baseType.GetProperty(prop.Name, prop.PropertyType) != null) + { + type = baseType; + } + else + { + break; + } + } + + ArrayList list = (ArrayList)groups[type]; + + if (list == null) + { + groups[type] = list = new ArrayList(); + } + + list.Add(prop); + } + } + } + + ArrayList sorted = new ArrayList(groups); + + sorted.Sort(new GroupComparer(objectType)); + + return sorted; + } + + public static object GetObjectFromString(Type t, string s) + { + if (t == typeof(string)) + { + return s; + } + + if (t == typeof(byte) || t == typeof(sbyte) || t == typeof(short) || t == typeof(ushort) || t == typeof(int) || t == typeof(uint) || t == typeof(long) || t == typeof(ulong)) + { + if (s.StartsWith("0x")) + { + if (t == typeof(ulong) || t == typeof(uint) || t == typeof(ushort) || t == typeof(byte)) + { + return Convert.ChangeType(Convert.ToUInt64(s.Substring(2), 16), t); + } + + return Convert.ChangeType(Convert.ToInt64(s.Substring(2), 16), t); + } + + return Convert.ChangeType(s, t); + } + + if (t == typeof(double) || t == typeof(float)) + { + return Convert.ChangeType(s, t); + } + if (t.IsDefined(typeof(ParsableAttribute), false)) + { + MethodInfo parseMethod = t.GetMethod("Parse", new[] { typeof(string) }); + + return parseMethod.Invoke(null, new object[] { s }); + } + + throw new Exception("bad"); + } + + private class PropertySorter : IComparer + { + public static readonly PropertySorter Instance = new(); + + private PropertySorter() + { + } + + public int Compare(object x, object y) + { + if (x == null && y == null) + { + return 0; + } + + if (x == null) + { + return -1; + } + + if (y == null) + { + return 1; + } + + PropertyInfo a = x as PropertyInfo; + PropertyInfo b = y as PropertyInfo; + + if (a == null || b == null) + { + throw new ArgumentException(); + } + + return a.Name.CompareTo(b.Name); + } + } + + private class GroupComparer : IComparer + { + private readonly Type m_Start; + + public GroupComparer(Type start) => m_Start = start; + + private static readonly Type typeofObject = typeof(object); + + private int GetDistance(Type type) + { + Type current = m_Start; + + int dist; + + for (dist = 0; current != null && current != typeofObject && current != type; ++dist) + { + current = current.BaseType; + } + + return dist; + } + + public int Compare(object x, object y) + { + if (x == null && y == null) + { + return 0; + } + + if (x == null) + { + return -1; + } + + if (y == null) + { + return 1; + } + + if (!(x is DictionaryEntry de1) || !(y is DictionaryEntry de2)) + { + throw new ArgumentException(); + } + + Type a = (Type)de1.Key; + Type b = (Type)de2.Key; + + return GetDistance(a).CompareTo(GetDistance(b)); + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetCustomEnumGump.cs b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetCustomEnumGump.cs new file mode 100644 index 000000000..a45d8f1da --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetCustomEnumGump.cs @@ -0,0 +1,47 @@ +using Server.Commands; +using Server.Network; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Server.Gumps; + +public class XmlSetCustomEnumGump : XmlSetListOptionGump +{ + private readonly string[] m_Names; + public XmlSetCustomEnumGump(PropertyInfo prop, Mobile mobile, object o, Stack stack, int propspage, ArrayList list, string[] names) : base(prop, mobile, o, stack, propspage, list, names, null) + { + m_Names = names; + } + + public override void OnResponse(NetState sender, RelayInfo relayInfo) + { + int index = relayInfo.ButtonID - 1; + + if (index >= 0 && index < m_Names.Length) + { + try + { + MethodInfo info = m_Property.PropertyType.GetMethod("Parse", new[] { typeof(string) }); + + CommandLogging.LogChangeProperty(m_Mobile, m_Object, m_Property.Name, m_Names[index]); + + if (info != null) + { + m_Property.SetValue(m_Object, info.Invoke(null, new object[] { m_Names[index] }), null); + } + else if (m_Property.PropertyType == typeof(Enum) || m_Property.PropertyType.IsSubclassOf(typeof(Enum))) + { + m_Property.SetValue(m_Object, Enum.Parse(m_Property.PropertyType, m_Names[index], false), null); + } + } + catch + { + m_Mobile.SendMessage("An exception was caught. The property may not have changed."); + } + } + + m_Mobile.SendGump(new XmlPropertiesGump(m_Mobile, m_Object, m_Stack, m_List, m_Page)); + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetGump.cs b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetGump.cs new file mode 100644 index 000000000..e3decf23f --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetGump.cs @@ -0,0 +1,250 @@ +using Server.Commands; +using Server.HuePickers; +using Server.Network; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Server.Gumps; + +public class XmlSetGump : Gump +{ + private readonly PropertyInfo m_Property; + private readonly Mobile m_Mobile; + private readonly object m_Object; + private readonly Stack m_Stack; + private readonly int m_Page; + private readonly ArrayList m_List; + + public static readonly bool OldStyle = PropsConfig.OldStyle; + + public static readonly int GumpOffsetX = PropsConfig.GumpOffsetX; + public static readonly int GumpOffsetY = PropsConfig.GumpOffsetY; + + public static readonly int TextHue = PropsConfig.TextHue; + public static readonly int TextOffsetX = PropsConfig.TextOffsetX; + + public static readonly int OffsetGumpID = PropsConfig.OffsetGumpID; + public static readonly int EntryGumpID = PropsConfig.EntryGumpID; + public static readonly int BackGumpID = PropsConfig.BackGumpID; + public static readonly int SetGumpID = PropsConfig.SetGumpID; + + public static readonly int SetWidth = PropsConfig.SetWidth; + public static readonly int SetOffsetX = PropsConfig.SetOffsetX, SetOffsetY = PropsConfig.SetOffsetY; + public static readonly int SetButtonID1 = PropsConfig.SetButtonID1; + public static readonly int SetButtonID2 = PropsConfig.SetButtonID2; + + public static readonly int OffsetSize = PropsConfig.OffsetSize; + + public static readonly int EntryHeight = PropsConfig.EntryHeight; + public static readonly int BorderSize = PropsConfig.BorderSize; + + private static readonly int EntryWidth = 212; + + private static readonly int TotalWidth = OffsetSize + EntryWidth + OffsetSize + SetWidth + OffsetSize; + private static readonly int TotalHeight = OffsetSize + 2 * (EntryHeight + OffsetSize); + + private static readonly int BackWidth = BorderSize + TotalWidth + BorderSize; + private static readonly int BackHeight = BorderSize + TotalHeight + BorderSize; + + public XmlSetGump(PropertyInfo prop, Mobile mobile, object o, Stack stack, int page, ArrayList list) : base(GumpOffsetX, GumpOffsetY) + { + m_Property = prop; + m_Mobile = mobile; + m_Object = o; + m_Stack = stack; + m_Page = page; + m_List = list; + + bool canNull = !prop.PropertyType.IsValueType; + bool canDye = prop.IsDefined(typeof(HueAttribute), false); + + int xextend = 0; + if (prop.PropertyType == typeof(string)) + { + xextend = 300; + } + + object val = prop.GetValue(m_Object, null); + + var initialText = val == null ? "" : val.ToString(); + + AddPage(0); + + AddBackground(0, 0, BackWidth + xextend, BackHeight + (canNull ? EntryHeight + OffsetSize : 0) + (canDye ? EntryHeight + OffsetSize : 0), BackGumpID); + AddImageTiled(BorderSize, BorderSize, TotalWidth + xextend - (OldStyle ? SetWidth + OffsetSize : 0), TotalHeight + (canNull ? EntryHeight + OffsetSize : 0) + (canDye ? EntryHeight + OffsetSize : 0), OffsetGumpID); + + int x = BorderSize + OffsetSize; + int y = BorderSize + OffsetSize; + + AddImageTiled(x, y, EntryWidth + xextend, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth + xextend - TextOffsetX, EntryHeight, TextHue, prop.Name); + x += EntryWidth + xextend + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + AddImageTiled(x, y, EntryWidth + xextend, EntryHeight, EntryGumpID); + AddTextEntry(x + TextOffsetX, y, EntryWidth + xextend - TextOffsetX, EntryHeight, TextHue, 0, initialText); + x += EntryWidth + xextend + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, 1); + + if (canNull) + { + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + AddImageTiled(x, y, EntryWidth + xextend, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth + xextend - TextOffsetX, EntryHeight, TextHue, "Null"); + x += EntryWidth + xextend + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, 2); + } + + if (canDye) + { + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + AddImageTiled(x, y, EntryWidth + xextend, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth + xextend - TextOffsetX, EntryHeight, TextHue, "Hue Picker"); + x += EntryWidth + xextend + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, 3); + } + } + + private class InternalPicker : HuePicker + { + private readonly PropertyInfo m_Property; + private readonly Mobile m_Mobile; + private readonly object m_Object; + private readonly Stack m_Stack; + private readonly int m_Page; + private readonly ArrayList m_List; + + public InternalPicker(PropertyInfo prop, Mobile mobile, object o, Stack stack, int page, ArrayList list) : base(((IHued)o).HuedItemID) + { + m_Property = prop; + m_Mobile = mobile; + m_Object = o; + m_Stack = stack; + m_Page = page; + m_List = list; + } + + public override void OnResponse(int hue) + { + try + { + CommandLogging.LogChangeProperty(m_Mobile, m_Object, m_Property.Name, hue.ToString()); + m_Property.SetValue(m_Object, hue, null); + } + catch + { + m_Mobile.SendMessage("An exception was caught. The property may not have changed."); + } + + m_Mobile.SendGump(new XmlPropertiesGump(m_Mobile, m_Object, m_Stack, m_List, m_Page)); + } + } + + public override void OnResponse(NetState sender, RelayInfo info) + { + object toSet; + bool shouldSet, shouldSend = true; + + switch (info.ButtonID) + { + case 1: + { + TextRelay text = info.GetTextEntry(0); + + if (text != null) + { + try + { + toSet = XmlPropertiesGump.GetObjectFromString(m_Property.PropertyType, text.Text); + shouldSet = true; + } + catch + { + toSet = null; + shouldSet = false; + m_Mobile.SendMessage("Bad format"); + } + } + else + { + toSet = null; + shouldSet = false; + } + + break; + } + case 2: // Null + { + toSet = null; + shouldSet = true; + + break; + } + case 3: // Hue Picker + { + toSet = null; + shouldSet = false; + shouldSend = false; + + m_Mobile.SendHuePicker(new InternalPicker(m_Property, m_Mobile, m_Object, m_Stack, m_Page, m_List)); + + break; + } + default: + { + toSet = null; + shouldSet = false; + + break; + } + } + + if (shouldSet) + { + try + { + CommandLogging.LogChangeProperty(m_Mobile, m_Object, m_Property.Name, toSet == null ? "(null)" : toSet.ToString()); + m_Property.SetValue(m_Object, toSet, null); + } + catch + { + m_Mobile.SendMessage("An exception was caught. The property may not have changed."); + } + } + + if (shouldSend) + { + m_Mobile.SendGump(new XmlPropertiesGump(m_Mobile, m_Object, m_Stack, m_List, m_Page)); + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetListOptionGump.cs b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetListOptionGump.cs new file mode 100644 index 000000000..ab7e061a1 --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetListOptionGump.cs @@ -0,0 +1,196 @@ +using Server.Commands; +using Server.Network; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Server.Gumps; + +public class XmlSetListOptionGump : Gump +{ + protected PropertyInfo m_Property; + protected Mobile m_Mobile; + protected object m_Object; + protected Stack m_Stack; + protected int m_Page; + protected ArrayList m_List; + + public static readonly bool OldStyle = PropsConfig.OldStyle; + + public static readonly int GumpOffsetX = PropsConfig.GumpOffsetX; + public static readonly int GumpOffsetY = PropsConfig.GumpOffsetY; + + public static readonly int TextHue = PropsConfig.TextHue; + public static readonly int TextOffsetX = PropsConfig.TextOffsetX; + + public static readonly int OffsetGumpID = PropsConfig.OffsetGumpID; + public static readonly int HeaderGumpID = PropsConfig.HeaderGumpID; + public static readonly int EntryGumpID = PropsConfig.EntryGumpID; + public static readonly int BackGumpID = PropsConfig.BackGumpID; + public static readonly int SetGumpID = PropsConfig.SetGumpID; + + public static readonly int SetWidth = PropsConfig.SetWidth; + public static readonly int SetOffsetX = PropsConfig.SetOffsetX, SetOffsetY = PropsConfig.SetOffsetY; + public static readonly int SetButtonID1 = PropsConfig.SetButtonID1; + public static readonly int SetButtonID2 = PropsConfig.SetButtonID2; + + public static readonly int PrevWidth = PropsConfig.PrevWidth; + public static readonly int PrevOffsetX = PropsConfig.PrevOffsetX, PrevOffsetY = PropsConfig.PrevOffsetY; + public static readonly int PrevButtonID1 = PropsConfig.PrevButtonID1; + public static readonly int PrevButtonID2 = PropsConfig.PrevButtonID2; + + public static readonly int NextWidth = PropsConfig.NextWidth; + public static readonly int NextOffsetX = PropsConfig.NextOffsetX, NextOffsetY = PropsConfig.NextOffsetY; + public static readonly int NextButtonID1 = PropsConfig.NextButtonID1; + public static readonly int NextButtonID2 = PropsConfig.NextButtonID2; + + public static readonly int OffsetSize = PropsConfig.OffsetSize; + + public static readonly int EntryHeight = PropsConfig.EntryHeight; + public static readonly int BorderSize = PropsConfig.BorderSize; + + private static readonly int EntryWidth = 212; + private static readonly int EntryCount = 13; + + private static readonly int TotalWidth = OffsetSize + EntryWidth + OffsetSize + SetWidth + OffsetSize; + + private static readonly int BackWidth = BorderSize + TotalWidth + BorderSize; + + private static readonly bool PrevLabel = OldStyle, NextLabel = OldStyle; + + private static readonly int PrevLabelOffsetX = PrevWidth + 1; + private static readonly int PrevLabelOffsetY = 0; + + private static readonly int NextLabelOffsetX = -29; + private static readonly int NextLabelOffsetY = 0; + + protected object[] m_Values; + + public XmlSetListOptionGump(PropertyInfo prop, Mobile mobile, object o, Stack stack, int propspage, ArrayList list, string[] names, object[] values) : base(GumpOffsetX, GumpOffsetY) + { + m_Property = prop; + m_Mobile = mobile; + m_Object = o; + m_Stack = stack; + m_Page = propspage; + m_List = list; + + m_Values = values; + + int pages = (names.Length + EntryCount - 1) / EntryCount; + int index = 0; + + for (int page = 1; page <= pages; ++page) + { + AddPage(page); + + int start = (page - 1) * EntryCount; + int count = names.Length - start; + + if (count > EntryCount) + { + count = EntryCount; + } + + int totalHeight = OffsetSize + (count + 2) * (EntryHeight + OffsetSize); + int backHeight = BorderSize + totalHeight + BorderSize; + + AddBackground(0, 0, BackWidth, backHeight, BackGumpID); + AddImageTiled(BorderSize, BorderSize, TotalWidth - (OldStyle ? SetWidth + OffsetSize : 0), totalHeight, OffsetGumpID); + + + + int x = BorderSize + OffsetSize; + int y = BorderSize + OffsetSize; + + int emptyWidth = TotalWidth - PrevWidth - NextWidth - OffsetSize * 4 - (OldStyle ? SetWidth + OffsetSize : 0); + + AddImageTiled(x, y, PrevWidth, EntryHeight, HeaderGumpID); + + if (page > 1) + { + AddButton(x + PrevOffsetX, y + PrevOffsetY, PrevButtonID1, PrevButtonID2, 0, GumpButtonType.Page, page - 1); + + if (PrevLabel) + { + AddLabel(x + PrevLabelOffsetX, y + PrevLabelOffsetY, TextHue, "Previous"); + } + } + + x += PrevWidth + OffsetSize; + + if (!OldStyle) + { + AddImageTiled(x - (OldStyle ? OffsetSize : 0), y, emptyWidth + (OldStyle ? OffsetSize * 2 : 0), EntryHeight, HeaderGumpID); + } + + x += emptyWidth + OffsetSize; + + if (!OldStyle) + { + AddImageTiled(x, y, NextWidth, EntryHeight, HeaderGumpID); + } + + if (page < pages) + { + AddButton(x + NextOffsetX, y + NextOffsetY, NextButtonID1, NextButtonID2, 0, GumpButtonType.Page, page + 1); + + if (NextLabel) + { + AddLabel(x + NextLabelOffsetX, y + NextLabelOffsetY, TextHue, "Next"); + } + } + + + + AddRect(0, prop.Name, 0); + + for (int i = 0; i < count; ++i) + { + AddRect(i + 1, names[index], ++index); + } + } + } + + private void AddRect(int index, string str, int button) + { + int x = BorderSize + OffsetSize; + int y = BorderSize + OffsetSize + (index + 1) * (EntryHeight + OffsetSize); + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, str); + + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + if (button != 0) + { + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, button); + } + } + + public override void OnResponse(NetState sender, RelayInfo info) + { + int index = info.ButtonID - 1; + + if (index >= 0 && index < m_Values.Length) + { + try + { + object toSet = m_Values[index]; + CommandLogging.LogChangeProperty(m_Mobile, m_Object, m_Property.Name, toSet == null ? "(-null-)" : toSet.ToString()); + m_Property.SetValue(m_Object, toSet, null); + } + catch + { + m_Mobile.SendMessage("An exception was caught. The property may not have changed."); + } + } + + m_Mobile.SendGump(new XmlPropertiesGump(m_Mobile, m_Object, m_Stack, m_List, m_Page)); + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetObjectGump.cs b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetObjectGump.cs new file mode 100644 index 000000000..d0230e414 --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetObjectGump.cs @@ -0,0 +1,302 @@ +using Server.Commands; +using Server.Commands.Generic; +using Server.Network; +using Server.Prompts; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Server.Gumps; + +public class XmlSetObjectGump : Gump +{ + private readonly PropertyInfo m_Property; + private readonly Mobile m_Mobile; + private readonly object m_Object; + private readonly Stack m_Stack; + private readonly Type m_Type; + private readonly int m_Page; + private readonly ArrayList m_List; + + public static readonly bool OldStyle = PropsConfig.OldStyle; + + public static readonly int GumpOffsetX = PropsConfig.GumpOffsetX; + public static readonly int GumpOffsetY = PropsConfig.GumpOffsetY; + + public static readonly int TextHue = PropsConfig.TextHue; + public static readonly int TextOffsetX = PropsConfig.TextOffsetX; + + public static readonly int OffsetGumpID = PropsConfig.OffsetGumpID; + public static readonly int EntryGumpID = PropsConfig.EntryGumpID; + public static readonly int BackGumpID = PropsConfig.BackGumpID; + public static readonly int SetGumpID = PropsConfig.SetGumpID; + + public static readonly int SetWidth = PropsConfig.SetWidth; + public static readonly int SetOffsetX = PropsConfig.SetOffsetX, SetOffsetY = PropsConfig.SetOffsetY; + public static readonly int SetButtonID1 = PropsConfig.SetButtonID1; + public static readonly int SetButtonID2 = PropsConfig.SetButtonID2; + + public static readonly int OffsetSize = PropsConfig.OffsetSize; + + public static readonly int EntryHeight = PropsConfig.EntryHeight; + public static readonly int BorderSize = PropsConfig.BorderSize; + + private static readonly int EntryWidth = 212; + + private static readonly int TotalWidth = OffsetSize + EntryWidth + OffsetSize + SetWidth + OffsetSize; + private static readonly int TotalHeight = OffsetSize + 5 * (EntryHeight + OffsetSize); + + private static readonly int BackWidth = BorderSize + TotalWidth + BorderSize; + private static readonly int BackHeight = BorderSize + TotalHeight + BorderSize; + + public XmlSetObjectGump(PropertyInfo prop, Mobile mobile, object o, Stack stack, Type type, int page, ArrayList list) : base(GumpOffsetX, GumpOffsetY) + { + m_Property = prop; + m_Mobile = mobile; + m_Object = o; + m_Stack = stack; + m_Type = type; + m_Page = page; + m_List = list; + + string initialText = XmlPropertiesGump.ValueToString(o, prop); + + AddPage(0); + + AddBackground(0, 0, BackWidth, BackHeight, BackGumpID); + AddImageTiled(BorderSize, BorderSize, TotalWidth - (OldStyle ? SetWidth + OffsetSize : 0), TotalHeight, OffsetGumpID); + + int x = BorderSize + OffsetSize; + int y = BorderSize + OffsetSize; + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, prop.Name); + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, initialText); + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, 1); + + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, "Change by Serial"); + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, 2); + + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, "Nullify"); + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, 3); + + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, "View Properties"); + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, 4); + } + + private class InternalPrompt : Prompt + { + private readonly PropertyInfo m_Property; + private readonly Mobile m_Mobile; + private readonly object m_Object; + private readonly Stack m_Stack; + private readonly Type m_Type; + private readonly int m_Page; + private readonly ArrayList m_List; + + public InternalPrompt(PropertyInfo prop, Mobile mobile, object o, Stack stack, Type type, int page, ArrayList list) + { + m_Property = prop; + m_Mobile = mobile; + m_Object = o; + m_Stack = stack; + m_Type = type; + m_Page = page; + m_List = list; + } + + public override void OnCancel(Mobile from) + { + m_Mobile.SendGump(new XmlSetObjectGump(m_Property, m_Mobile, m_Object, m_Stack, m_Type, m_Page, m_List)); + } + + public override void OnResponse(Mobile from, string text) + { + object toSet; + bool shouldSet; + + try + { + var serial = Utility.ToUInt32(text); + toSet = World.FindEntity((Serial)serial); + + if (toSet == null) + { + shouldSet = false; + m_Mobile.SendMessage("No object with that serial was found."); + } + else if (!m_Type.IsInstanceOfType(toSet)) + { + toSet = null; + shouldSet = false; + m_Mobile.SendMessage("The object with that serial could not be assigned to a property of type : {0}", m_Type.Name); + } + else + { + shouldSet = true; + } + } + catch + { + toSet = null; + shouldSet = false; + m_Mobile.SendMessage("Bad format"); + } + + if (shouldSet) + { + try + { + CommandLogging.LogChangeProperty(m_Mobile, m_Object, m_Property.Name, toSet.ToString()); + m_Property.SetValue(m_Object, toSet, null); + } + catch + { + m_Mobile.SendMessage("An exception was caught. The property may not have changed."); + } + } + + m_Mobile.SendGump(new XmlSetObjectGump(m_Property, m_Mobile, m_Object, m_Stack, m_Type, m_Page, m_List)); + } + } + + public override void OnResponse(NetState sender, RelayInfo info) + { + bool shouldSet, shouldSend = true; + object viewProps = null; + + switch (info.ButtonID) + { + case 0: // closed + { + m_Mobile.SendGump(new XmlPropertiesGump(m_Mobile, m_Object, m_Stack, m_List, m_Page)); + shouldSet = false; + shouldSend = false; + + break; + } + case 1: // Change by Target + { + m_Mobile.Target = new XmlSetObjectTarget(m_Property, m_Mobile, m_Object, m_Stack, m_Type, m_Page, m_List); + shouldSet = false; + shouldSend = false; + break; + } + case 2: // Change by Serial + { + shouldSet = false; + shouldSend = false; + m_Mobile.SendMessage("Enter the serial you wish to find:"); + m_Mobile.Prompt = new InternalPrompt(m_Property, m_Mobile, m_Object, m_Stack, m_Type, m_Page, m_List); + + break; + } + case 3: // Nullify + { + shouldSet = true; + break; + } + case 4: // View Properties + { + shouldSet = false; + + object obj = m_Property.GetValue(m_Object, null); + + if (obj == null) + { + m_Mobile.SendMessage("The property is null and so you cannot view its properties."); + } + else if (!BaseCommand.IsAccessible(m_Mobile, obj)) + { + m_Mobile.SendMessage("You may not view their properties."); + } + else + { + viewProps = obj; + } + + break; + } + default: + { + shouldSet = false; + break; + } + } + + if (shouldSet) + { + try + { + CommandLogging.LogChangeProperty(m_Mobile, m_Object, m_Property.Name, "(null)"); + m_Property.SetValue(m_Object, null, null); + } + catch + { + m_Mobile.SendMessage("An exception was caught. The property may not have changed."); + } + } + + if (shouldSend) + { + m_Mobile.SendGump(new XmlSetObjectGump(m_Property, m_Mobile, m_Object, m_Stack, m_Type, m_Page, m_List)); + } + + if (viewProps != null) + { + m_Mobile.SendGump(new XmlPropertiesGump(m_Mobile, viewProps)); + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetObjectTarget.cs b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetObjectTarget.cs new file mode 100644 index 000000000..9c6393b40 --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetObjectTarget.cs @@ -0,0 +1,72 @@ +using Server.Commands; +using Server.Items; +using Server.Targeting; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Server.Gumps; + +public class XmlSetObjectTarget : Target +{ + private readonly PropertyInfo m_Property; + private readonly Mobile m_Mobile; + private readonly object m_Object; + private readonly Stack m_Stack; + private readonly Type m_Type; + private readonly int m_Page; + private readonly ArrayList m_List; + + public XmlSetObjectTarget(PropertyInfo prop, Mobile mobile, object o, Stack stack, Type type, int page, ArrayList list) : base(-1, false, TargetFlags.None) + { + m_Property = prop; + m_Mobile = mobile; + m_Object = o; + m_Stack = stack; + m_Type = type; + m_Page = page; + m_List = list; + } + + protected override void OnTarget(Mobile from, object targeted) + { + try + { + if (m_Type == typeof(Type)) + { + targeted = targeted.GetType(); + } + else if ((m_Type == typeof(BaseAddon) || m_Type.IsAssignableFrom(typeof(BaseAddon))) && targeted is AddonComponent component) + { + targeted = component.Addon; + } + + if (m_Type.IsInstanceOfType(targeted)) + { + CommandLogging.LogChangeProperty(m_Mobile, m_Object, m_Property.Name, targeted.ToString()); + m_Property.SetValue(m_Object, targeted, null); + } + else + { + m_Mobile.SendMessage("That cannot be assigned to a property of type : {0}", m_Type.Name); + } + } + catch + { + m_Mobile.SendMessage("An exception was caught. The property may not have changed."); + } + } + + protected override void OnTargetFinish(Mobile from) + { + if (m_Type == typeof(Type)) + { + from.SendGump(new XmlPropertiesGump(m_Mobile, m_Object, m_Stack, m_List, m_Page)); + } + else + { + from.SendGump(new XmlSetObjectGump(m_Property, m_Mobile, m_Object, m_Stack, m_Type, m_Page, m_List)); + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetPoint2DGump.cs b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetPoint2DGump.cs new file mode 100644 index 000000000..eb5f634cb --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetPoint2DGump.cs @@ -0,0 +1,235 @@ +using Server.Commands; +using Server.Network; +using Server.Targeting; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Server.Gumps; + +public class XmlSetPoint2DGump : Gump +{ + private readonly PropertyInfo m_Property; + private readonly Mobile m_Mobile; + private readonly object m_Object; + private readonly Stack m_Stack; + private readonly int m_Page; + private readonly ArrayList m_List; + + public static readonly bool OldStyle = PropsConfig.OldStyle; + + public static readonly int GumpOffsetX = PropsConfig.GumpOffsetX; + public static readonly int GumpOffsetY = PropsConfig.GumpOffsetY; + + public static readonly int TextHue = PropsConfig.TextHue; + public static readonly int TextOffsetX = PropsConfig.TextOffsetX; + + public static readonly int OffsetGumpID = PropsConfig.OffsetGumpID; + public static readonly int EntryGumpID = PropsConfig.EntryGumpID; + public static readonly int BackGumpID = PropsConfig.BackGumpID; + public static readonly int SetGumpID = PropsConfig.SetGumpID; + + public static readonly int SetWidth = PropsConfig.SetWidth; + public static readonly int SetOffsetX = PropsConfig.SetOffsetX, SetOffsetY = PropsConfig.SetOffsetY; + public static readonly int SetButtonID1 = PropsConfig.SetButtonID1; + public static readonly int SetButtonID2 = PropsConfig.SetButtonID2; + + public static readonly int OffsetSize = PropsConfig.OffsetSize; + + public static readonly int EntryHeight = PropsConfig.EntryHeight; + public static readonly int BorderSize = PropsConfig.BorderSize; + + private static readonly int CoordWidth = 105; + private static readonly int EntryWidth = CoordWidth + OffsetSize + CoordWidth; + + private static readonly int TotalWidth = OffsetSize + EntryWidth + OffsetSize + SetWidth + OffsetSize; + private static readonly int TotalHeight = OffsetSize + 4 * (EntryHeight + OffsetSize); + + private static readonly int BackWidth = BorderSize + TotalWidth + BorderSize; + private static readonly int BackHeight = BorderSize + TotalHeight + BorderSize; + + public XmlSetPoint2DGump(PropertyInfo prop, Mobile mobile, object o, Stack stack, int page, ArrayList list) : base(GumpOffsetX, GumpOffsetY) + { + m_Property = prop; + m_Mobile = mobile; + m_Object = o; + m_Stack = stack; + m_Page = page; + m_List = list; + + Point2D p = (Point2D)prop.GetValue(o, null); + + AddPage(0); + + AddBackground(0, 0, BackWidth, BackHeight, BackGumpID); + AddImageTiled(BorderSize, BorderSize, TotalWidth - (OldStyle ? SetWidth + OffsetSize : 0), TotalHeight, OffsetGumpID); + + int x = BorderSize + OffsetSize; + int y = BorderSize + OffsetSize; + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, prop.Name); + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, "Use your location"); + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, 1); + + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, "Target a location"); + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, 2); + + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + AddImageTiled(x, y, CoordWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, CoordWidth - TextOffsetX, EntryHeight, TextHue, "X:"); + AddTextEntry(x + 16, y, CoordWidth - 16, EntryHeight, TextHue, 0, p.X.ToString()); + x += CoordWidth + OffsetSize; + + AddImageTiled(x, y, CoordWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, CoordWidth - TextOffsetX, EntryHeight, TextHue, "Y:"); + AddTextEntry(x + 16, y, CoordWidth - 16, EntryHeight, TextHue, 1, p.Y.ToString()); + x += CoordWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, 3); + } + + private class InternalTarget : Target + { + private readonly PropertyInfo m_Property; + private readonly Mobile m_Mobile; + private readonly object m_Object; + private readonly Stack m_Stack; + private readonly int m_Page; + private readonly ArrayList m_List; + + public InternalTarget(PropertyInfo prop, Mobile mobile, object o, Stack stack, int page, ArrayList list) : base(-1, true, TargetFlags.None) + { + m_Property = prop; + m_Mobile = mobile; + m_Object = o; + m_Stack = stack; + m_Page = page; + m_List = list; + } + + protected override void OnTarget(Mobile from, object targeted) + { + IPoint3D p = targeted as IPoint3D; + + if (p != null) + { + try + { + CommandLogging.LogChangeProperty(m_Mobile, m_Object, m_Property.Name, new Point2D(p.X, p.Y).ToString()); + m_Property.SetValue(m_Object, new Point2D(p.X, p.Y), null); + } + catch + { + m_Mobile.SendMessage("An exception was caught. The property may not have changed."); + } + } + } + + protected override void OnTargetFinish(Mobile from) + { + m_Mobile.SendGump(new XmlPropertiesGump(m_Mobile, m_Object, m_Stack, m_List, m_Page)); + } + } + + public override void OnResponse(NetState sender, RelayInfo info) + { + Point2D toSet; + bool shouldSet, shouldSend; + + switch (info.ButtonID) + { + case 1: // Current location + { + toSet = new Point2D(m_Mobile.X, m_Mobile.Y); + shouldSet = true; + shouldSend = true; + + break; + } + case 2: // Pick location + { + m_Mobile.Target = new InternalTarget(m_Property, m_Mobile, m_Object, m_Stack, m_Page, m_List); + + toSet = Point2D.Zero; + shouldSet = false; + shouldSend = false; + + break; + } + case 3: // Use values + { + TextRelay x = info.GetTextEntry(0); + TextRelay y = info.GetTextEntry(1); + + toSet = new Point2D(x == null ? 0 : Utility.ToInt32(x.Text), y == null ? 0 : Utility.ToInt32(y.Text)); + shouldSet = true; + shouldSend = true; + + break; + } + default: + { + toSet = Point2D.Zero; + shouldSet = false; + shouldSend = true; + + break; + } + } + + if (shouldSet) + { + try + { + CommandLogging.LogChangeProperty(m_Mobile, m_Object, m_Property.Name, toSet.ToString()); + m_Property.SetValue(m_Object, toSet, null); + } + catch + { + m_Mobile.SendMessage("An exception was caught. The property may not have changed."); + } + } + + if (shouldSend) + { + m_Mobile.SendGump(new XmlPropertiesGump(m_Mobile, m_Object, m_Stack, m_List, m_Page)); + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetPoint3DGump.cs b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetPoint3DGump.cs new file mode 100644 index 000000000..b0fa3c0a3 --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetPoint3DGump.cs @@ -0,0 +1,241 @@ +using Server.Commands; +using Server.Network; +using Server.Targeting; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Server.Gumps; + +public class XmlSetPoint3DGump : Gump +{ + private readonly PropertyInfo m_Property; + private readonly Mobile m_Mobile; + private readonly object m_Object; + private readonly Stack m_Stack; + private readonly int m_Page; + private readonly ArrayList m_List; + + public static readonly bool OldStyle = PropsConfig.OldStyle; + + public static readonly int GumpOffsetX = PropsConfig.GumpOffsetX; + public static readonly int GumpOffsetY = PropsConfig.GumpOffsetY; + + public static readonly int TextHue = PropsConfig.TextHue; + public static readonly int TextOffsetX = PropsConfig.TextOffsetX; + + public static readonly int OffsetGumpID = PropsConfig.OffsetGumpID; + public static readonly int EntryGumpID = PropsConfig.EntryGumpID; + public static readonly int BackGumpID = PropsConfig.BackGumpID; + public static readonly int SetGumpID = PropsConfig.SetGumpID; + + public static readonly int SetWidth = PropsConfig.SetWidth; + public static readonly int SetOffsetX = PropsConfig.SetOffsetX, SetOffsetY = PropsConfig.SetOffsetY; + public static readonly int SetButtonID1 = PropsConfig.SetButtonID1; + public static readonly int SetButtonID2 = PropsConfig.SetButtonID2; + + public static readonly int OffsetSize = PropsConfig.OffsetSize; + + public static readonly int EntryHeight = PropsConfig.EntryHeight; + public static readonly int BorderSize = PropsConfig.BorderSize; + + private static readonly int CoordWidth = 70; + private static readonly int EntryWidth = CoordWidth + OffsetSize + CoordWidth + OffsetSize + CoordWidth; + + private static readonly int TotalWidth = OffsetSize + EntryWidth + OffsetSize + SetWidth + OffsetSize; + private static readonly int TotalHeight = OffsetSize + 4 * (EntryHeight + OffsetSize); + + private static readonly int BackWidth = BorderSize + TotalWidth + BorderSize; + private static readonly int BackHeight = BorderSize + TotalHeight + BorderSize; + + public XmlSetPoint3DGump(PropertyInfo prop, Mobile mobile, object o, Stack stack, int page, ArrayList list) : base(GumpOffsetX, GumpOffsetY) + { + m_Property = prop; + m_Mobile = mobile; + m_Object = o; + m_Stack = stack; + m_Page = page; + m_List = list; + + Point3D p = (Point3D)prop.GetValue(o, null); + + AddPage(0); + + AddBackground(0, 0, BackWidth, BackHeight, BackGumpID); + AddImageTiled(BorderSize, BorderSize, TotalWidth - (OldStyle ? SetWidth + OffsetSize : 0), TotalHeight, OffsetGumpID); + + int x = BorderSize + OffsetSize; + int y = BorderSize + OffsetSize; + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, prop.Name); + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, "Use your location"); + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, 1); + + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, "Target a location"); + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, 2); + + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + AddImageTiled(x, y, CoordWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, CoordWidth - TextOffsetX, EntryHeight, TextHue, "X:"); + AddTextEntry(x + 16, y, CoordWidth - 16, EntryHeight, TextHue, 0, p.X.ToString()); + x += CoordWidth + OffsetSize; + + AddImageTiled(x, y, CoordWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, CoordWidth - TextOffsetX, EntryHeight, TextHue, "Y:"); + AddTextEntry(x + 16, y, CoordWidth - 16, EntryHeight, TextHue, 1, p.Y.ToString()); + x += CoordWidth + OffsetSize; + + AddImageTiled(x, y, CoordWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, CoordWidth - TextOffsetX, EntryHeight, TextHue, "Z:"); + AddTextEntry(x + 16, y, CoordWidth - 16, EntryHeight, TextHue, 2, p.Z.ToString()); + x += CoordWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, 3); + } + + private class InternalTarget : Target + { + private readonly PropertyInfo m_Property; + private readonly Mobile m_Mobile; + private readonly object m_Object; + private readonly Stack m_Stack; + private readonly int m_Page; + private readonly ArrayList m_List; + + public InternalTarget(PropertyInfo prop, Mobile mobile, object o, Stack stack, int page, ArrayList list) : base(-1, true, TargetFlags.None) + { + m_Property = prop; + m_Mobile = mobile; + m_Object = o; + m_Stack = stack; + m_Page = page; + m_List = list; + } + + protected override void OnTarget(Mobile from, object targeted) + { + IPoint3D p = targeted as IPoint3D; + + if (p != null) + { + try + { + CommandLogging.LogChangeProperty(m_Mobile, m_Object, m_Property.Name, new Point3D(p).ToString()); + m_Property.SetValue(m_Object, new Point3D(p), null); + } + catch + { + m_Mobile.SendMessage("An exception was caught. The property may not have changed."); + } + } + } + + protected override void OnTargetFinish(Mobile from) + { + m_Mobile.SendGump(new XmlPropertiesGump(m_Mobile, m_Object, m_Stack, m_List, m_Page)); + } + } + + public override void OnResponse(NetState sender, RelayInfo info) + { + Point3D toSet; + bool shouldSet, shouldSend; + + switch (info.ButtonID) + { + case 1: // Current location + { + toSet = m_Mobile.Location; + shouldSet = true; + shouldSend = true; + + break; + } + case 2: // Pick location + { + m_Mobile.Target = new InternalTarget(m_Property, m_Mobile, m_Object, m_Stack, m_Page, m_List); + + toSet = Point3D.Zero; + shouldSet = false; + shouldSend = false; + + break; + } + case 3: // Use values + { + TextRelay x = info.GetTextEntry(0); + TextRelay y = info.GetTextEntry(1); + TextRelay z = info.GetTextEntry(2); + + toSet = new Point3D(x == null ? 0 : Utility.ToInt32(x.Text), y == null ? 0 : Utility.ToInt32(y.Text), z == null ? 0 : Utility.ToInt32(z.Text)); + shouldSet = true; + shouldSend = true; + + break; + } + default: + { + toSet = Point3D.Zero; + shouldSet = false; + shouldSend = true; + + break; + } + } + + if (shouldSet) + { + try + { + CommandLogging.LogChangeProperty(m_Mobile, m_Object, m_Property.Name, toSet.ToString()); + m_Property.SetValue(m_Object, toSet, null); + } + catch + { + m_Mobile.SendMessage("An exception was caught. The property may not have changed."); + } + } + + if (shouldSend) + { + m_Mobile.SendGump(new XmlPropertiesGump(m_Mobile, m_Object, m_Stack, m_List, m_Page)); + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetTimeSpanGump.cs b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetTimeSpanGump.cs new file mode 100644 index 000000000..2e1c45a6e --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlPropsGumps/XmlSetTimeSpanGump.cs @@ -0,0 +1,240 @@ +using Server.Commands; +using Server.Network; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Server.Gumps; + +public class XmlSetTimeSpanGump : Gump +{ + private readonly PropertyInfo m_Property; + private readonly Mobile m_Mobile; + private readonly object m_Object; + private readonly Stack m_Stack; + private readonly int m_Page; + private readonly ArrayList m_List; + + public static readonly bool OldStyle = PropsConfig.OldStyle; + + public static readonly int GumpOffsetX = PropsConfig.GumpOffsetX; + public static readonly int GumpOffsetY = PropsConfig.GumpOffsetY; + + public static readonly int TextHue = PropsConfig.TextHue; + public static readonly int TextOffsetX = PropsConfig.TextOffsetX; + + public static readonly int OffsetGumpID = PropsConfig.OffsetGumpID; + public static readonly int EntryGumpID = PropsConfig.EntryGumpID; + public static readonly int BackGumpID = PropsConfig.BackGumpID; + public static readonly int SetGumpID = PropsConfig.SetGumpID; + + public static readonly int SetWidth = PropsConfig.SetWidth; + public static readonly int SetOffsetX = PropsConfig.SetOffsetX, SetOffsetY = PropsConfig.SetOffsetY; + public static readonly int SetButtonID1 = PropsConfig.SetButtonID1; + public static readonly int SetButtonID2 = PropsConfig.SetButtonID2; + + public static readonly int OffsetSize = PropsConfig.OffsetSize; + + public static readonly int EntryHeight = PropsConfig.EntryHeight; + public static readonly int BorderSize = PropsConfig.BorderSize; + + private static readonly int EntryWidth = 212; + + private static readonly int TotalWidth = OffsetSize + EntryWidth + OffsetSize + SetWidth + OffsetSize; + private static readonly int TotalHeight = OffsetSize + 7 * (EntryHeight + OffsetSize); + + private static readonly int BackWidth = BorderSize + TotalWidth + BorderSize; + private static readonly int BackHeight = BorderSize + TotalHeight + BorderSize; + + public XmlSetTimeSpanGump(PropertyInfo prop, Mobile mobile, object o, Stack stack, int page, ArrayList list) : base(GumpOffsetX, GumpOffsetY) + { + m_Property = prop; + m_Mobile = mobile; + m_Object = o; + m_Stack = stack; + m_Page = page; + m_List = list; + + TimeSpan ts = (TimeSpan)prop.GetValue(o, null); + + AddPage(0); + + AddBackground(0, 0, BackWidth, BackHeight, BackGumpID); + AddImageTiled(BorderSize, BorderSize, TotalWidth - (OldStyle ? SetWidth + OffsetSize : 0), TotalHeight, OffsetGumpID); + + AddRect(0, prop.Name, 0, -1); + AddRect(1, ts.ToString(), 0, -1); + AddRect(2, "Zero", 1, -1); + AddRect(3, "From H:M:S", 2, -1); + AddRect(4, "H:", 3, 0); + AddRect(5, "M:", 4, 1); + AddRect(6, "S:", 5, 2); + } + + private void AddRect(int index, string str, int button, int text) + { + int x = BorderSize + OffsetSize; + int y = BorderSize + OffsetSize + index * (EntryHeight + OffsetSize); + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y, EntryWidth - TextOffsetX, EntryHeight, TextHue, str); + + if (text != -1) + { + AddTextEntry(x + 16 + TextOffsetX, y, EntryWidth - TextOffsetX - 16, EntryHeight, TextHue, text, ""); + } + + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + if (button != 0) + { + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, button); + } + } + + public override void OnResponse(NetState sender, RelayInfo info) + { + TimeSpan toSet; + bool shouldSet, shouldSend; + + TextRelay h = info.GetTextEntry(0); + TextRelay m = info.GetTextEntry(1); + TextRelay s = info.GetTextEntry(2); + + switch (info.ButtonID) + { + case 1: // Zero + { + toSet = TimeSpan.Zero; + shouldSet = true; + shouldSend = true; + + break; + } + case 2: // From H:M:S + { + if (h != null && m != null && s != null) + { + try + { + toSet = TimeSpan.Parse($"{h.Text}:{m.Text}:{s.Text}"); + shouldSet = true; + shouldSend = true; + + break; + } + catch + { + } + } + + toSet = TimeSpan.Zero; + shouldSet = false; + shouldSend = false; + + break; + } + case 3: // From H + { + if (h != null) + { + try + { + toSet = TimeSpan.FromHours(Utility.ToDouble(h.Text)); + shouldSet = true; + shouldSend = true; + + break; + } + catch + { + } + } + + toSet = TimeSpan.Zero; + shouldSet = false; + shouldSend = false; + + break; + } + case 4: // From M + { + if (m != null) + { + try + { + toSet = TimeSpan.FromMinutes(Utility.ToDouble(m.Text)); + shouldSet = true; + shouldSend = true; + + break; + } + catch + { + } + } + + toSet = TimeSpan.Zero; + shouldSet = false; + shouldSend = false; + + break; + } + case 5: // From S + { + if (s != null) + { + try + { + toSet = TimeSpan.FromSeconds(Utility.ToDouble(s.Text)); + shouldSet = true; + shouldSend = true; + + break; + } + catch + { + } + } + + toSet = TimeSpan.Zero; + shouldSet = false; + shouldSend = false; + + break; + } + default: + { + toSet = TimeSpan.Zero; + shouldSet = false; + shouldSend = true; + + break; + } + } + + if (shouldSet) + { + try + { + CommandLogging.LogChangeProperty(m_Mobile, m_Object, m_Property.Name, toSet.ToString()); + m_Property.SetValue(m_Object, toSet, null); + } + catch + { + m_Mobile.SendMessage("An exception was caught. The property may not have changed."); + } + } + + if (shouldSend) + { + m_Mobile.SendGump(new XmlPropertiesGump(m_Mobile, m_Object, m_Stack, m_List, m_Page)); + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlSpawner.cs b/Projects/UOContent/Engines/XMLSpawner/XmlSpawner.cs new file mode 100644 index 000000000..abb16a41c --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlSpawner.cs @@ -0,0 +1,12776 @@ +using Server.Accounting; +using Server.Commands; +using Server.Commands.Generic; +using Server.ContextMenus; +using Server.Items; +using Server.Network; +using Server.Targeting; +using System; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Xml; +using Server.Engines.Spawners; + +namespace Server.Mobiles; + +public class XmlSpawner : Item, ISpawner +{ + public enum TODModeType { Realtime, Gametime } + + public enum SpawnPositionType { Random, RowFill, ColFill, Perimeter, Player, Waypoint, RelXY, DeltaLocation, Location, Wet, Tiles, NoTiles, ItemID, NoItemID } + + public class SpawnPositionInfo + { + public SpawnPositionType positionType; + public Mobile trigMob; + public string[] positionArgs; + + public SpawnPositionInfo(SpawnPositionType positiontype, Mobile trigmob, string[] positionargs) + { + positionType = positiontype; + trigMob = trigmob; + positionArgs = positionargs; + } + } + + public class MovementInfo + { + public Mobile trigMob; + public Point3D trigLocation = Point3D.Zero; + + public MovementInfo(Mobile m) + { + trigMob = m; + if (m != null) + { + trigLocation = m.Location; + } + } + } + + public const string Version = "5.0"; // RUADUCK's EDIT + public const byte MaxLoops = 10; //maximum number of recursive calls from spawner to itself. this is to prevent stack overflow from xmlspawner scripting + private const int ShowBoundsItemId = 14089; // 14089 Fire Column // 3555 Campfire // 8708 Skull Pole + private const string SpawnDataSetName = "Spawns"; + private const string SpawnTablePointName = "Points"; + private const int SpawnFitSize = 16; // Normal wall/door height for a mobile is 20 to walk through + private static int BaseItemId = 0x1F1C; // Purple Magic Crystal + private static int ShowItemId = 0x3E57; // ships mast + private static int defaultTriggerSound = 0x1F4; // click and sparkle sound by default (0x1F4) , click sound (0x3A4) + public static string XmlSpawnDir = "XmlSpawner"; // default directory for saving/loading .xml files with [xmlload [xmlsave + private const int MaxSmartSectorListSize = 1024; // maximum sector list size for use in smart spawning. This gives a 512x512 tile range. + + private static string defwaypointname; // default waypoint name will get assigned in Initialize + private const string XmlTableName = "Properties"; + private const string XmlDataSetName = "XmlSpawner"; + public static AccessLevel DiskAccessLevel = AccessLevel.Administrator; // minimum access level required by commands that can access the disk such as XmlLoad, XmlSave, and the Save function of XmlEdit +#if (RESTRICTConstructible) + public static AccessLevel ConstructibleAccessLevel = AccessLevel.GameMaster; // only allow spawning of objects that have Constructible access restrictions at this level or lower. Must define RESTRICTConstructible to enable this. +#endif + private static int MaxMoveCheck = 10; // limit number of players that can be checked for triggering in a single OnMovement tick + + // specifies the level at which smartspawning will be triggered. Players with AccessLevel above this will not trigger smartspawning unless unhidden. + public static AccessLevel SmartSpawnAccessLevel = AccessLevel.Player; + + // define the default values used in making spawners + private static TimeSpan defMinDelay = TimeSpan.FromMinutes(5); + private static TimeSpan defMaxDelay = TimeSpan.FromMinutes(10); + private static TimeSpan defMinRefractory = TimeSpan.FromMinutes(0); + private static TimeSpan defMaxRefractory = TimeSpan.FromMinutes(0); + private static TimeSpan defTODStart = TimeSpan.FromMinutes(0); + private static TimeSpan defTODEnd = TimeSpan.FromMinutes(0); + private static TimeSpan defDuration = TimeSpan.FromMinutes(0); + private static TimeSpan defDespawnTime = TimeSpan.FromHours(0); + private static bool defIsGroup; + private static int defTeam; + private static int defProximityTriggerSound = defaultTriggerSound; + private static int defAmount = 1; + private static bool defRelativeHome = true; + private static int defSpawnRange = 5; + private static int defHomeRange = 5; + private static double defTriggerProbability = 1; + private static int defProximityRange = -1; + private static readonly int defKillReset = 1; + private static TODModeType defTODMode = TODModeType.Realtime; + + private static Timer m_GlobalSectorTimer; + private static bool SmartSpawningSystemEnabled; + + private static WarnTimer2 m_WarnTimer; + + // hash table for optimizing HoldSmartSpawning method invocation + private static Dictionary holdSmartSpawningHash; + + public static int seccount; + + // sector hashtable for each map + private static readonly Dictionary>[] GlobalSectorTable = new Dictionary>[6]; + + private string m_Name = string.Empty; + private string m_UniqueId = string.Empty; + private bool m_PlayerCreated; + private bool m_HomeRangeIsRelative; + private int m_Team; + private int m_HomeRange; + // added a amount parameter for stacked item spawns + private int m_StackAmount; + // this is actually redundant with the width height spec for spawning area + // just an easier way of specifying it + private int m_SpawnRange; + private int m_Count; + private TimeSpan m_MinDelay; + private TimeSpan m_MaxDelay; + // added a duration parameter for time-limited spawns + private TimeSpan m_Duration; + public List m_SpawnObjects = new(); // List of objects to spawn + private DateTime m_End; + private DateTime m_RefractEnd; + private DateTime m_DurEnd; + private SpawnerTimer m_Timer; + private InternalTimer m_DurTimer; + private InternalTimer3 m_RefractoryTimer; + private bool m_Running; + private bool m_Group; + private int m_X; + private int m_Y; + private int m_Width; + private int m_Height; + private WayPoint m_WayPoint; + + private Static m_ShowContainerStatic; + private bool m_proximityActivated; + private bool m_refractActivated; + private bool m_durActivated; + private TimeSpan m_TODStart; + private TimeSpan m_TODEnd; + // time after proximity activation when the spawn cannot be reactivated + private TimeSpan m_MinRefractory; + private TimeSpan m_MaxRefractory; + private string m_ItemTriggerName; + private string m_NoItemTriggerName; + private Item m_ObjectPropertyItem; + private string m_ObjectPropertyName; + public string status_str; + public int m_killcount; + // added proximity range sensor + private int m_ProximityRange; + // sound played when a proximity triggered spawner is tripped by a player + // set this to zero if you dont want to hear anything + private int m_ProximityTriggerSound; + private string m_ProximityTriggerMessage; + private string m_SpeechTrigger; + private bool m_speechTriggerActivated; + private string m_MobPropertyName; + private string m_MobTriggerName; + private string m_PlayerPropertyName; + private double m_TriggerProbability = defTriggerProbability; + private Mobile m_mob_who_triggered; + private Item m_SetPropertyItem; + + private bool m_skipped; + private int m_KillReset = defKillReset; // number of spawn ticks that pass without kills before killcount gets reset to zero + private int m_spawncheck; + private TODModeType m_TODMode = TODModeType.Realtime; + private string m_GumpState; + private bool m_ExternalTriggering; + private bool m_ExternalTrigger; + private int m_SequentialSpawning = -1; // off by default + private DateTime m_SeqEnd; + private Region m_Region; // 2004.02.08 :: Omega Red + private string m_RegionName = string.Empty; // 2004.02.08 :: Omega Red + private AccessLevel m_TriggerAccessLevel = AccessLevel.Player; + + public List m_TextEntryBook; + private XmlSpawnerGump m_SpawnerGump; + + private bool m_AllowGhostTriggering; + private bool m_AllowNPCTriggering; + private string m_ConfigFile; + private bool m_OnHold; + private bool m_HoldSequence; + private bool m_SpawnOnTrigger; + + private List m_MovementList; + private MovementTimer m_MovementTimer; + internal List m_KeywordTagList = new(); + + public List RecentSpawnerSearchList = null; + public List RecentItemSearchList = null; + public List RecentMobileSearchList = null; + private TimeSpan m_DespawnTime; + + private string m_SkillTrigger; + private SkillName m_skill_that_triggered; + private bool m_FreeRun; // override for all other triggering modes + + private Map currentmap; + + public bool m_IsInactivated; + private bool m_SmartSpawning; + private SectorTimer m_SectorTimer; + + private List m_ShowBoundsItems = new(); + + public List PropertyInfoList = null; // used to optimize property info lookup used by set and get property methods. + + private Dictionary> spawnPositionWayTable; // used to optimize #waypoint lookup + + private bool inrespawn; + + private List sectorList; + + private bool m_DisableGlobalAutoReset; + + private Point3D mostRecentSpawnPosition = Point3D.Zero; + + // does not decay + public override bool Decays => false; + // is not counted in the normal item count + public override bool IsVirtualItem => true; + + private bool m_skillTriggerActivated; + private SkillName m_SkillTriggerName; + private double m_SkillTriggerMin; + private double m_SkillTriggerMax; + private int m_SkillTriggerSuccess; + + public bool DebugThis { get; set; } = false; + + public int MovingPlayerCount { get; set; } + + public int FastestPlayerSpeed { get; set; } + + public int NearbyPlayerCount + { + get + { + int count = 0; + if (ProximityRange >= 0) + { + IPooledEnumerable eable = GetMobilesInRange(ProximityRange); + foreach (Mobile m in eable) + { + if (m != null && m.Player) + { + count++; + } + } + + eable.Free(); + } + return count; + } + } + + public Point3D MostRecentSpawnPosition + { + get => mostRecentSpawnPosition; + set => mostRecentSpawnPosition = value; + } + + public TimeSpan GameTOD + { + get + { + int hours; + int minutes; + + Clock.GetTime(Map, Location.X, Location.Y, out hours, out minutes); + return new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, DateTime.UtcNow.Day, hours, minutes, 0).TimeOfDay; + } + } + + public TimeSpan RealTOD => DateTime.UtcNow.TimeOfDay; + + public int RealDay => DateTime.UtcNow.Day; + + public int RealMonth => DateTime.UtcNow.Month; + + public DayOfWeek RealDayOfWeek => DateTime.UtcNow.DayOfWeek; + + public MoonPhase MoonPhase => Clock.GetMoonPhase(Map, Location.X, Location.Y); + + public XmlSpawnerGump SpawnerGump + { + get => m_SpawnerGump; + set => m_SpawnerGump = value; + } + + public bool DisableGlobalAutoReset { get => m_DisableGlobalAutoReset; + set => m_DisableGlobalAutoReset = value; + } + + public bool DoDefrag + { + get => false; + set + { + if (value) + { + Defrag(true); + } + } + } + + private readonly bool sectorIsActive = false; + private bool UseSectorActivate; + + public bool SingleSector => UseSectorActivate; + + public bool InActivationRange(Sector s1, Sector s2) + { + // check to see if the sectors are within +- 2 of one another + if (s1 == null || s2 == null) + { + return false; + } + + return Math.Abs(s1.X - s2.X) < 3 && Math.Abs(s1.Y - s2.Y) < 3; + } + + public bool HasDamagedOrDistantSpawns + { + get + { + Sector ssec = Map.GetSector(Location); + // go through the spawn lists + foreach (SpawnObject so in m_SpawnObjects) + { + for (int x = 0; x < so.SpawnedObjects.Count; x++) + { + object o = so.SpawnedObjects[x]; + + if (o is BaseCreature creature) + { + // if the mob is damaged or outside of smartspawning detection range then return true + if (creature.Hits < creature.HitsMax || creature.Mana < creature.ManaMax || creature.Stam < creature.StamMax || creature.Map != Map) + { + return true; + } + + // if the spawn moves into a sector that is not activatable from a sector on the sector list then dont smartspawn + if (creature.Map != null && creature.Map != Map.Internal) + { + Sector bsec = creature.Map.GetSector(creature.Location); + + if (UseSectorActivate) + { + // is it in activatable range of the sector the spawner is in + if (!InActivationRange(bsec, ssec)) + { + return true; + } + } + else + { + bool outofsec = true; + + if (sectorList != null) + { + foreach (Sector s in sectorList) + { + // is the creatures sector within activation range of any of the sectors in the list + if (InActivationRange(bsec, s)) + { + outofsec = false; + break; + } + } + } + + if (outofsec) + { + return true; + } + } + } + } + } + } + + return false; + } + } + + private static int totalSectorsMonitored; + + public bool HasActiveSectors + { + get + { + if (!SmartSpawning || Map == null || Map == Map.Internal) + { + return false; + } + + // is this a region spawner? + if (m_Region != null) + { + var players = m_Region.GetPlayers(); + + if (players == null || players.Count == 0) + { + return false; + } + + // confirm that players with the proper access level are present + foreach (Mobile m in players) + { + if (m != null && (m.AccessLevel <= SmartSpawnAccessLevel || !m.Hidden)) + { + return true; + } + } + return false; + } + // is this a single sector spawner? + if (UseSectorActivate) + { + return sectorIsActive; + } + + // if there is no sector list made for this spawner then create one. + if (sectorList == null) + { + Point3D loc = Location; + sectorList = new List(); + + // is this container held? + if (Parent != null) + { + if (RootParent is Mobile mobile) + { + loc = ((Mobile)RootParent).Location; + } + else + if (RootParent is Item item) + { + loc = ((Item)RootParent).Location; + } + } + + // find the max detection range by examining both spawnrange + // note, sectors will activate when within +-2 sectors + int bufferzone = 2 * Map.SectorSize; + int x1 = m_X - bufferzone; + int width = m_Width + 2 * bufferzone; + int y1 = m_Y - bufferzone; + int height = m_Height + 2 * bufferzone; + + // go through all of the sectors within the SpawnRange of the spawner to see if any are active + for (int x = x1; x <= x1 + width; x += Map.SectorSize) + { + for (int y = y1; y <= y1 + height; y += Map.SectorSize) + { + Sector s = Map.GetSector(new Point3D(x, y, loc.Z)); + + if (s == null) + { + continue; + } + + // dont add any redundant sectors + bool duplicate = false; + foreach (Sector olds in sectorList) + { + if (olds == s) + { + duplicate = true; + break; + } + } + if (!duplicate) + { + sectorList.Add(s); + + if (GlobalSectorTable[Map.MapID] == null) + { + GlobalSectorTable[Map.MapID] = new Dictionary>(); + } + + // add this sector and the spawner associated with it to the global sector table + List spawnerlist; + if (GlobalSectorTable[Map.MapID].TryGetValue(s, out spawnerlist)) //.Contains(s)) + { + //List spawnerlist = GlobalSectorTable[Map.MapID][s]; + if (spawnerlist == null) + { + //GlobalSectorTable[Map.MapID].Remove(s); + spawnerlist = new List(); + //GlobalSectorTable[Map.MapID].Add(s, spawnerlist); + GlobalSectorTable[Map.MapID][s] = spawnerlist; + } + + if (!spawnerlist.Contains(this)) + { + spawnerlist.Add(this); + + } + } + else + { + spawnerlist = new List(); + spawnerlist.Add(this); + // add a new entry to the table + GlobalSectorTable[Map.MapID][s] = spawnerlist; + } + + totalSectorsMonitored++; + + // add some sanity checking here + if (sectorList.Count > MaxSmartSectorListSize) + { + SmartSpawning = false; + + // log it + try + { + Console.WriteLine("SmartSpawning disabled at {0} {1} : Range too large.", loc, Map); + + using (StreamWriter op = new StreamWriter("badspawn.log", true)) + { + op.WriteLine("{0} SmartSpawning disabled at {1} {2} : Range too large.", DateTime.UtcNow, loc, Map); + op.WriteLine(); + } + } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + + return true; + } + } + } + } + + UseSectorActivate = false; + } + + _TraceStart(2); + // go through the sectorlist and see if any of the sectors are active + + foreach (Sector s in sectorList) + { + if (s != null && s.Active && s.Clients != null && s.Clients.Count > 0) + { + // confirm that players with the proper access level are present + foreach (NetState ns in s.Clients) + { + var m = ns.Mobile; + if (m != null && (m.AccessLevel <= SmartSpawnAccessLevel || !m.Hidden)) + { + return true; + } + } + _TraceEnd(2); + } + seccount++; + } + _TraceEnd(2); + return false; + } + } + + public int SecCount => seccount; + + public bool IsInactivated + { + get => m_IsInactivated; + set => m_IsInactivated = value; + } + + public int ActiveSectorCount + { + get + { + if (sectorList != null) + { + return sectorList.Count; + } + + return 0; + } + } + + public bool PlayerCreated + { + get => m_PlayerCreated; + set => m_PlayerCreated = value; + } + + public bool OnHold + { + get + { + if (m_OnHold) + { + return true; + } + + // determine whether there are any keywordtags with the hold flag + if (m_KeywordTagList == null || m_KeywordTagList.Count == 0) + { + return false; + } + + foreach (BaseXmlSpawner.KeywordTag sot in m_KeywordTagList) + { + // check for any keyword tag with the holdspawn flag + if (sot != null && !sot.Deleted && (sot.Flags & BaseXmlSpawner.KeywordFlags.HoldSpawn) != 0) + { + return true; + } + } + // no hold flags were set + return false; + } + set => m_OnHold = value; + } + + public string AddSpawn + { + get => null; + set + { + if (!string.IsNullOrEmpty(value)) + { + string str = value.Trim(); + string typestr = BaseXmlSpawner.ParseObjectType(str); + + Type type = AssemblyHandler.FindTypeByName(typestr); + + if (type != null) + { + m_SpawnObjects.Add(new SpawnObject(str, 1)); + } + else + { + // check for special keywords + if (typestr != null && (BaseXmlSpawner.IsTypeOrItemKeyword(typestr) || typestr.IndexOf("{") != -1 || typestr.StartsWith("*") || typestr.StartsWith("#"))) + { + m_SpawnObjects.Add(new SpawnObject(str, 1)); + } + else + { + status_str = string.Format("{0} is not a valid type name.", str); + } + } + InvalidateProperties(); + } + } + } + + public string UniqueId => m_UniqueId; + + // does not perform a defrag, so less accurate but can be used while looping through world object enums + public int SafeCurrentCount => SafeTotalSpawnedObjects; + + public bool FreeRun + { + get => m_FreeRun; + set => m_FreeRun = value; + } + + public bool CanFreeSpawn + { + get + { + // allow free spawning if proximity sensing is off and if all of the potential free-spawning triggers are disabled + if (Running && m_ProximityRange == -1 && + string.IsNullOrEmpty(m_ObjectPropertyName) && + (string.IsNullOrEmpty(m_MobPropertyName) || + m_MobTriggerName == null || m_MobTriggerName.Length == 0) && + !m_ExternalTriggering) + { + return true; + } + + return false; + } + } + + public SpawnObject[] SpawnObjects + { + get => m_SpawnObjects.ToArray(); + set + { + if (value != null && value.Length > 0) + { + + foreach (SpawnObject so in value) + { + if (so == null) + { + continue; + } + + bool AlreadyInList = false; + + // Check if the new array has an existing spawn object + foreach (SpawnObject TheSpawn in m_SpawnObjects) + { + if (TheSpawn.TypeName.ToUpper() == so.TypeName.ToUpper()) + { + AlreadyInList = true; + break; + } + } + + // Does this item need to be added + if (!AlreadyInList) + { + // This is a new spawn object so add it to the array (deep copy) + m_SpawnObjects.Add(new SpawnObject(so.TypeName, so.ActualMaxCount, so.SubGroup, so.SequentialResetTime, so.SequentialResetTo, so.KillsNeeded, + so.RestrictKillsToSubgroup, so.ClearOnAdvance, so.MinDelay, so.MaxDelay, so.SpawnsPerTick, so.PackRange)); + } + } + + if (SpawnObjects.Length < 1) + { + Stop(); + } + + InvalidateProperties(); + } + } + } + + public bool HoldSequence + { + get + { + // check to see if any keyword tags have the holdsequence flag set, or whether the spawner holdsequence flag is set + if (m_HoldSequence) + { + return true; + } + + // determine whether there are any keywordtags with the hold flag + if (m_KeywordTagList == null || m_KeywordTagList.Count == 0) + { + return false; + } + + foreach (BaseXmlSpawner.KeywordTag sot in m_KeywordTagList) + { + // check for any keyword tag with the holdsequence flag + if (sot != null && !sot.Deleted && (sot.Flags & BaseXmlSpawner.KeywordFlags.HoldSequence) != 0) + { + return true; + } + } + + // no hold flags were set + return false; + } + + set => m_HoldSequence = value; + } + + public bool CanSpawn + { + get + { + if (OnHold) + { + return false; + } + + if (m_Group) + { + if (TotalSpawnedObjects <= 0) + { + return true; + } + + return false; + } + + if (IsFull) + { + return false; + } + + return true; + } + } + + // test for a full spawner + public bool IsFull + { + get + { + int nobj = TotalSpawnedObjects; + + return nobj >= m_Count || nobj >= TotalSpawnObjectCount; + } + } + + // this can be used in loops over world objects since it will not defrag and potentially modify the world object lists + public int SafeTotalSpawnedObjects + { + get + { + if (m_SpawnObjects == null) + { + return 0; + } + + int count = 0; + + foreach (SpawnObject so in m_SpawnObjects) + { + count += so.SpawnedObjects.Count; + } + + return count; + } + } + + public int TotalSpawnedObjects + { + get + { + if (m_SpawnObjects == null) + { + return 0; + } + + // defrag so that accurately reflects currently active spawns + Defrag(true); + + int count = 0; + + foreach (SpawnObject so in m_SpawnObjects) + { + count += so.SpawnedObjects.Count; + } + + return count; + } + } + + public bool isEmpty() + { + if (m_SpawnObjects == null) + { + return true; + } + + foreach (SpawnObject so in m_SpawnObjects) + { + if (so.SpawnedObjects != null && so.SpawnedObjects.Count > 0) + { + if (so.SpawnedObjects[0] is Mobile) + { + return false; + } + } + + } + return true; + } + + public int TotalSpawnObjectCount + { + get + { + int count = 0; + + foreach (SpawnObject so in m_SpawnObjects) + { + count += so.MaxCount; + } + + return count; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool GumpReset + { + + set + { + if (value) + { + m_SpawnerGump = null; + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public Region SpawnRegion + { + get => m_Region; + set + { + // force a re-update of the smart spawning sector list the next time it is accessed + ResetSectorList(); + + m_Region = value; + + m_RegionName = m_Region?.Name; + } + } + + // 2004.02.08 :: Omega Red + [CommandProperty(AccessLevel.GameMaster)] + public string RegionName + { + get => m_RegionName; + set + { + // force a re-update of the smart spawning sector list the next time it is accessed + ResetSectorList(); + + m_RegionName = value; + + if (string.IsNullOrEmpty(value)) + { + m_Region = null; + return; + } + + if (Region.Regions.Count == 0) // after world load, before region load + { + return; + } + + foreach (Region region in Region.Regions) + { + if (string.Compare(region.Name, m_RegionName, true) == 0) + { + m_Region = region; + m_RegionName = region.Name; + //InvalidateProperties(); + return; + } + } + status_str = $"invalid region: {value}"; + m_Region = null; + } + } + + + [CommandProperty(AccessLevel.GameMaster)] + public Point3D X1_Y1 + { + get => new(m_X, m_Y, Z); + set + { + // X1 and Y1 will initiate region specification + m_Width = 0; + m_Height = 0; + m_X = value.X; + m_Y = value.Y; + + // reset the sector list + ResetSectorList(); + + m_SpawnRange = 0; + + if (ShowBounds) + { + ShowBounds = false; + ShowBounds = true; + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public Point3D X2_Y2 + { + get => new(m_X + m_Width, m_Y + m_Height, Z); + set + { + int X2; + int Y2; + + int OriginalX2 = m_X + m_Width; + int OriginalY2 = m_Y + m_Height; + + // reset the sector list + ResetSectorList(); + + // now determine based upon the entered coordinate values what the lower left corner is + // lower left will be the min x and min y + // upper right will be max x max y + if (value.X < OriginalX2) + { + // ok, this is the proper x value for the lower left + m_X = value.X; + X2 = OriginalX2; + } + else + { + m_X = OriginalX2; + X2 = value.X; + } + + if (value.Y < OriginalY2) + { + // ok, this is the proper y value for the lower left + m_Y = value.Y; + Y2 = OriginalY2; + } + else + { + m_Y = OriginalY2; + Y2 = value.Y; + } + + m_Width = X2 - m_X; + m_Height = Y2 - m_Y; + + if (m_Width == m_Height) + { + m_SpawnRange = m_Width / 2; + } + else + { + m_SpawnRange = -1; + } + + if (m_HomeRangeIsRelative == false) + { + int NewHomeRange = m_Width > m_Height ? m_Height : m_Width; + m_HomeRange = NewHomeRange > 0 ? NewHomeRange : 0; + } + + //original test was for less than 1, changed it to less than zero (zero is a valid width, its the default in fact) + // Stop the spawner if the width or height is less than 1 + if (m_Width < 0 || m_Height < 0) + { + Running = false; + } + + InvalidateProperties(); + + if (ShowBounds) + { + ShowBounds = false; + ShowBounds = true; + } + } + } + + // added the spawnrange property. It sets both the XY and width/height parameters automatically. + // also doesnt mess with homerange like XY does + [CommandProperty(AccessLevel.GameMaster)] + public int SpawnRange + { + get => m_SpawnRange; + set + { + if (value < 0) + { + return; + } + + // reset the sector list + ResetSectorList(); + + m_SpawnRange = value; + m_Width = m_SpawnRange * 2; + m_Height = m_SpawnRange * 2; + + // dont set the bounding box locations if the initial location is 0,0 since this occurs when the item is just being made + // because m_X and m_Y are restored on loading, it creates problems with OnLocationChange which has to avoid applying translational + // adjustments to newly placed spawners (because the actual m_X and m_Y is associated with the original location, not the 0,0 location) + // basically, before placement, dont set m_X or m_Y to anything that needs to be adjusted later on + + if (Location.X == 0 && Location.Y == 0) + { + return; + } + + m_X = Location.X - m_SpawnRange; + m_Y = Location.Y - m_SpawnRange; + + if (ShowBounds) + { + ShowBounds = false; + ShowBounds = true; + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool ShowBounds + { + get => m_ShowBoundsItems != null && m_ShowBoundsItems.Count > 0; + set + { + if (value && ShowBounds == false) + { + if (m_ShowBoundsItems == null) + { + m_ShowBoundsItems = new List(); + } + + // Boundary lines + int ValidX1 = m_X; + int ValidX2 = m_X + m_Width; + int ValidY1 = m_Y; + int ValidY2 = m_Y + m_Height; + + for (int x = 0; x <= m_Width; x++) + { + int NewX = m_X + x; + for (int y = 0; y <= m_Height; y++) + { + int NewY = m_Y + y; + + if (NewX == ValidX1 || NewX == ValidX2 || NewX == ValidY1 || NewX == ValidY2 || NewY == ValidX1 || NewY == ValidX2 || NewY == ValidY1 || NewY == ValidY2) + { + // Add an object to show the spawn area + Static s = new Static(ShowBoundsItemId) + { + Visible = false + }; + s.MoveToWorld(new Point3D(NewX, NewY, Z), Map); + m_ShowBoundsItems.Add(s); + } + } + } + } + + if (value == false && m_ShowBoundsItems != null) + { + // Remove all of the items from the array + foreach (Static s in m_ShowBoundsItems) + { + s.Delete(); + } + + m_ShowBoundsItems.Clear(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int MaxCount + { + get => m_Count; + set + { + m_Count = value; + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int CurrentCount => TotalSpawnedObjects; + + [CommandProperty(AccessLevel.GameMaster)] + public WayPoint WayPoint + { + get => m_WayPoint; + set => m_WayPoint = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool ExternalTriggering + { + get => m_ExternalTriggering; + set => m_ExternalTriggering = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool ExtTrigState + { + get => m_ExternalTrigger; + set => m_ExternalTrigger = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Running + { + get => m_Running; + set + { + // Don't start the spawner unless the height and width are valid + if (value && m_Width >= 0 && m_Height >= 0) + { + Start(); + } + else + { + Stop(); + } + + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int HomeRange + { + get => m_HomeRange; + set { m_HomeRange = value; InvalidateProperties(); } + } + + public Region Region { get; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool HomeRangeIsRelative + { + get => m_HomeRangeIsRelative; + set => m_HomeRangeIsRelative = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Team + { + get => m_Team; + set { m_Team = value; InvalidateProperties(); } + } + [CommandProperty(AccessLevel.GameMaster)] + public int StackAmount + { + get => m_StackAmount; + set => m_StackAmount = value; + } + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan MinDelay + { + get => m_MinDelay; + set + { + m_MinDelay = value; + // reset the spawn timer + DoTimer(); + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan MaxDelay + { + get => m_MaxDelay; + set + { + m_MaxDelay = value; + // reset the spawn timer + DoTimer(); + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int KillCount + { + get => m_killcount; + set => m_killcount = value; + } + [CommandProperty(AccessLevel.GameMaster)] + public int KillReset + { + get => m_KillReset; + set => m_KillReset = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public double TriggerProbability + { + get => m_TriggerProbability; + set => m_TriggerProbability = value; + } + + //added refractory period support + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan RefractMin + { + get => m_MinRefractory; + set => m_MinRefractory = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan RefractMax + { + get => m_MaxRefractory; + set => m_MaxRefractory = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan RefractoryOver + { + get + { + if (m_refractActivated) + { + return m_RefractEnd - DateTime.UtcNow; + } + + return TimeSpan.FromSeconds(0); + } + set => DoTimer3(value); + } + + [CommandProperty(AccessLevel.GameMaster)] + public string SetItemName + { + get + { + if (m_SetPropertyItem == null || m_SetPropertyItem.Deleted) + { + return null; + } + + return m_SetPropertyItem.Name; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public Item SetItem + { + get => m_SetPropertyItem; + set => m_SetPropertyItem = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public string MobTriggerProp + { + get => m_MobPropertyName; + set => m_MobPropertyName = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public string MobTriggerName + { + get => m_MobTriggerName; + set => m_MobTriggerName = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public Mobile MobTriggerId + { + get + { + if (m_MobTriggerName == null) + { + return null; + } + + // try to parse out the type information if it has also been saved + string[] typeargs = m_MobTriggerName.Split(",".ToCharArray(), 2); + string typestr = null; + string namestr = m_MobTriggerName; + + if (typeargs.Length > 1) + { + namestr = typeargs[0]; + typestr = typeargs[1]; + } + return BaseXmlSpawner.FindMobileByName(this, namestr, typestr); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public string PlayerTriggerProp + { + get => m_PlayerPropertyName; + set => m_PlayerPropertyName = value; + } + + // time of day activation + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan TODStart + { + get => m_TODStart; + set => m_TODStart = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan TODEnd + { + get => m_TODEnd; + set => m_TODEnd = value; + } + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan TOD + { + get + { + if (m_TODMode == TODModeType.Gametime) + { + int hours; + int minutes; + Clock.GetTime(Map, Location.X, Location.Y, out hours, out minutes); + return new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, DateTime.UtcNow.Day, hours, minutes, 0).TimeOfDay; + } + + return DateTime.UtcNow.TimeOfDay; + } + + } + + [CommandProperty(AccessLevel.GameMaster)] + public TODModeType TODMode + { + get => m_TODMode; + set => m_TODMode = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool TODInRange + { + get + { + if (m_TODStart == m_TODEnd) + { + return true; + } + + DateTime now; + + if (m_TODMode == TODModeType.Gametime) + { + int hours; + int minutes; + Clock.GetTime(Map, Location.X, Location.Y, out hours, out minutes); + now = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, DateTime.UtcNow.Day, hours, minutes, 0); + } + else + { + // calculate the time window + now = DateTime.UtcNow; + } + var day_start = new DateTime(now.Year, now.Month, now.Day); + // calculate the starting TOD window by adding the TODStart to day_start + var TOD_start = day_start + m_TODStart; + var TOD_end = day_start + m_TODEnd; + + // handle the case when TODstart is before midnight and end is after + + if (TOD_start > TOD_end) + { + if (now > TOD_start || now < TOD_end) + { + return true; + } + + return false; + } + + if (now > TOD_start && now < TOD_end) + { + return true; + } + + return false; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan DespawnTime + { + get => m_DespawnTime; + set => m_DespawnTime = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan Duration + { + get => m_Duration; + set + { + m_Duration = value; + InvalidateProperties(); + } + } + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan DurationOver + { + get + { + if (m_durActivated) + { + return m_DurEnd - DateTime.UtcNow; + } + + return TimeSpan.FromSeconds(0); + } + set => DoTimer2(value); + } + // proximity range parameter + [CommandProperty(AccessLevel.GameMaster)] + public int ProximityRange + { + get => m_ProximityRange; + set + { + m_ProximityRange = value; + InvalidateProperties(); + } + } + + + // proximity range activated? + [CommandProperty(AccessLevel.GameMaster)] + public bool ProximityActivated + { + get => m_proximityActivated; + set + { + + if (AllowTriggering) + { + ActivateTrigger(); + } + + m_proximityActivated = value; + + } + } + + // proximity trigger sound parameter + [CommandProperty(AccessLevel.GameMaster)] + public int ProximitySound + { + get => m_ProximityTriggerSound; + set => m_ProximityTriggerSound = value; + } + + // proximity trigger message parameter + [CommandProperty(AccessLevel.GameMaster)] + public string ProximityMsg + { + get => m_ProximityTriggerMessage; + set => m_ProximityTriggerMessage = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public string SpeechTrigger + { + get => m_SpeechTrigger; + set => m_SpeechTrigger = value; + } + + public string SkillTrigger + { + get => m_SkillTrigger; + set => m_SkillTrigger = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan NextSpawn + { + get + { + if (m_Running) + { + return m_End - DateTime.UtcNow; + } + + return TimeSpan.FromSeconds(0); + } + set + { + Start(); + DoTimer(value); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool SpawnOnTrigger + { + get => m_SpawnOnTrigger; + set => m_SpawnOnTrigger = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Group + { + get => m_Group; + set { m_Group = value; InvalidateProperties(); } + } + + [CommandProperty(AccessLevel.GameMaster)] + public string GumpState + { + get => m_GumpState; + set => m_GumpState = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public int SequentialSpawn + { + get => m_SequentialSpawning; + set => m_SequentialSpawning = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan NextSeqReset + { + get + { + if (m_Running && m_SeqEnd - DateTime.UtcNow > TimeSpan.Zero) + { + return m_SeqEnd - DateTime.UtcNow; + } + + return TimeSpan.FromSeconds(0); + } + set => m_SeqEnd = DateTime.UtcNow + value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public AccessLevel TriggerAccessLevel + { + get => m_TriggerAccessLevel; + set => m_TriggerAccessLevel = value; + } + + + [CommandProperty(AccessLevel.GameMaster)] + public bool DoRespawn + { + get => false; + set + { + // need to determine whether this is being set by the spawner during processing of a respawn entry + // if so then dont do it, otherwise you will infinitely recurse and crash with a stack overflow + if (value && !inrespawn) + { + TryRespawn(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool DoReset + { + get => false; + set { if (value) + { + Reset(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool AllowGhostTrig + { + get => m_AllowGhostTriggering; + set => m_AllowGhostTriggering = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool AllowNPCTrig + { + get => m_AllowNPCTriggering; + set => m_AllowNPCTriggering = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public string ConfigFile + { + get => m_ConfigFile; + set => m_ConfigFile = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool LoadConfig + { + get => false; + set { if (value) + { + LoadXmlConfig(ConfigFile); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public Mobile TriggerMob + { + get => m_mob_who_triggered; + set => m_mob_who_triggered = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool SmartSpawning + { + get => m_SmartSpawning; + set + { + m_SmartSpawning = value; + + if (m_SmartSpawning) + { + // if any spawner is smartspawning, then the smartspawning system is enabled + SmartSpawningSystemEnabled = true; + // check to see if the global sector timer is running + if (m_GlobalSectorTimer == null || !m_GlobalSectorTimer.Running) + { + // start the global smartspawning timer + DoGlobalSectorTimer(TimeSpan.FromSeconds(1)); + } + } + + //IsInactivated = false; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool IsEmpty => isEmpty(); + + public Guid Guid { get; } + public bool UnlinkOnTaming => true; + + [CommandProperty(AccessLevel.Developer)] + public bool ReturnOnDeactivate { get; set; } + + public Point3D HomeLocation => Location; + public int Range => HomeRange; + + public virtual void GetSpawnProperties(ISpawnable spawn, IPropertyList list) + { } + + public virtual void GetSpawnContextEntries(ISpawnable spawn, Mobile user, List list) + { } + + public void Remove(ISpawnable spawn) + { + if (m_SpawnObjects == null) + { + return; + } + + foreach (SpawnObject so in m_SpawnObjects) + { + for (int i = 0; i < so.SpawnedObjects.Count; ++i) + { + if (so.SpawnedObjects[i] == spawn) + { + so.SpawnedObjects.Remove(spawn); + if (SequentialSpawn >= 0 && so.RestrictKillsToSubgroup) + { + if (so.SubGroup == SequentialSpawn) + { + m_killcount++; + } + } + else + { + m_killcount++; + } + + return; + } + } + } + } + + public void RestoreISpawner() + { + // restore the Spawner assignments to all spawned objects + if (m_SpawnObjects == null) + { + return; + } + + foreach (SpawnObject so in m_SpawnObjects) + { + for (int i = 0; i < so.SpawnedObjects.Count; ++i) + { + object o = so.SpawnedObjects[i]; + if (o is Item item) + { + item.Spawner = this; + } + else if (o is Mobile mobile) + { + mobile.Spawner = this; + } + } + } + } + + public override void OnAfterDuped(Item newItem) + { + ((XmlSpawner)newItem).Running = false; // automatically turn off duped spawners + + base.OnAfterDuped(newItem); + } + + public override void OnMapChange() + { + base.OnMapChange(); + + currentmap = Map; + + ResetSectorList(); // reset the sector list for smart spawning + } + + public override void OnDoubleClick(Mobile from) + { + if (from == null || from.Deleted || from.AccessLevel < AccessLevel.GameMaster || m_SpawnerGump != null && SomeOneHasGumpOpen) + { + return; + } + + DeleteTextEntryBook(); // clear any text entry books that might still be around + + int x = 0; + int y = 0; + + Account acct = from.Account as Account; // read the text entries for default values + + if (acct != null) + { + XmlSpawnerDefaults.DefaultEntry defs = XmlSpawnerDefaults.GetDefaults(acct.ToString(), from.Name); + if (defs != null) + { + x = defs.SpawnerGumpX; + y = defs.SpawnerGumpY; + } + } + + XmlSpawnerGump g = new XmlSpawnerGump(this, x, y, 0, 0, 0); + from.SendGump(g); + } + + public override void GetProperties(IPropertyList list) + { + base.GetProperties(list); + + list.Add(m_Running ? 1060742 : 1060743); // Active - Inactive + + // add whitespace to the beginning to avoid any problem with names that begin with # and are interpreted as cliloc ids + list.Add(1042971, $" {Name}"); // ~1_val~ + list.Add(1060656, m_Count.ToString()); // amount to make: ~1_val~ + list.Add(1061169, m_HomeRange.ToString()); // range ~1_val~ + + int nlist_items = 6; + + if (m_Group) + { + list.Add(1060658 + 6 - nlist_items, $"{"group"}\t{m_Group}"); // ~1_val~: ~2_val~ + nlist_items--; + } + + if (m_Team != 0) + { + list.Add(1060658 + 6 - nlist_items, $"{"team"}\t{m_Team}"); // ~1_val~: ~2_val~ + nlist_items--; + } + + list.Add(1060658 + 6 - nlist_items, $"{"speed"}\t{m_MinDelay} to {m_MaxDelay}"); // ~1_val~: ~2_val~ + nlist_items--; + + // display the duration parameter in the prop gump if it is non-zero + if (m_Duration > TimeSpan.FromMinutes(0)) + { + list.Add(1060658 + 6 - nlist_items, $"{"Duration"}\t{m_Duration}"); + nlist_items--; + } + + // display the proximity range parameter in the prop gump if it is active + if (m_ProximityRange != -1) + { + list.Add(1060658 + 6 - nlist_items, $"{"ProximityRange"}\t{m_ProximityRange}"); + nlist_items--; + } + + if (m_SpawnObjects != null) + { + for (int i = 0; i < nlist_items && i < m_SpawnObjects.Count; ++i) + { + string typename = m_SpawnObjects[i].TypeName; + if (typename != null && typename.Length > 20) + { + typename = typename[..20]; + } + + typename = $" {typename}"; + + list.Add(1060658 + (6 - nlist_items) + i, $"{typename}\t{m_SpawnObjects[i].SpawnedObjects.Count}"); + } + } + } + + public override void OnDelete() + { + base.OnDelete(); + + if (ShowBounds) + { + ShowBounds = false; + } + + RemoveSpawnObjects(); + + // remove any text entry books that might still be attached to the spawner + DeleteTextEntryBook(); + + if (m_Timer != null) + { + m_Timer.Stop(); + } + + if (m_DurTimer != null) + { + m_DurTimer.Stop(); + } + + if (m_RefractoryTimer != null) + { + m_RefractoryTimer.Stop(); + } + + // if statics were added for marking container held spawners, delete them + if (m_ShowContainerStatic != null && !m_ShowContainerStatic.Deleted) + { + m_ShowContainerStatic.Delete(); + } + } + + static bool IgnoreLocationChange; + public override void OnLocationChange(Point3D oldLocation) + { + if (IgnoreLocationChange) + { + IgnoreLocationChange = false; + return; + } + + + // calculate the positional shift + if (oldLocation.X > 0 && oldLocation.Y > 0) + { + int diffx = X - oldLocation.X; + int diffy = Y - oldLocation.Y; + m_X += diffx; + m_Y += diffy; + } + else + { + // Keep the original dimensions the same (Width, Height), + // just recalculate the new top left corner + m_X = X - m_Width / 2; + m_Y = Y - m_Height / 2; + } + + // reset the sector list for smart spawning + ResetSectorList(); + + // Check if the spawner is showing its bounds + if (ShowBounds) + { + ShowBounds = false; + ShowBounds = true; + } + } + + public bool SomeOneHasGumpOpen + { + get + { + // go through all online mobiles and see if any have xmlspawner gumps open + var states = TcpServer.Instances; + + foreach (var ns in states) + { + var m = ns.Mobile; + if (m != null && m.HasGump()) + { + return true; + } + } + + return false; + } + } + + + public static void SpawnerGumpCallback(Mobile from, object invoker, string response) + { + // assign the response to the gumpstate + if (invoker is XmlSpawner xs) + { + xs.GumpState = response; + } + } + + public void DeleteTextEntryBook() + { + if (m_TextEntryBook != null) + { + foreach (XmlTextEntryBook s in m_TextEntryBook) + { + s.Delete(); + } + + m_TextEntryBook = null; + } + } + + private static bool IsConstructible(ConstructorInfo ctor) => ctor.IsDefined(typeof(ConstructibleAttribute), false); + + public static int ConvertToInt(string value) + { + if (value.StartsWith("0x")) + { + return Convert.ToInt32(value.Substring(2), 16); + } + + return Convert.ToInt32(value); + } + + public static void ExecuteAction(object attachedto, Mobile trigmob, string action) + { + Point3D loc = Point3D.Zero; + Map map = null; + if (attachedto is IEntity entity) + { + loc = entity.Location; + map = entity.Map; + } + + if (action == null || action.Length <= 0 || attachedto == null || map == null) + { + return; + } + + SpawnObject TheSpawn = new SpawnObject(null, 0) + { + TypeName = action + }; + string substitutedtypeName = BaseXmlSpawner.ApplySubstitution(null, attachedto, action); + string typeName = BaseXmlSpawner.ParseObjectType(substitutedtypeName); + + + string status_str; + if (BaseXmlSpawner.IsTypeOrItemKeyword(typeName)) + { + BaseXmlSpawner.SpawnTypeKeyword(attachedto, TheSpawn, typeName, substitutedtypeName, trigmob, map, out status_str); + } + else + { + // its a regular type descriptor so find out what it is + Type type = AssemblyHandler.FindTypeByName(typeName); + try + { + string[] arglist = BaseXmlSpawner.ParseString(substitutedtypeName, 3, "/"); + object o = CreateObject(type, arglist[0]); + + if (o == null) + { + status_str = $"invalid type specification: {arglist[0]}"; + } + else + if (o is Mobile mobile) + { + if (mobile is BaseCreature creature) + { + creature.Home = loc; // Spawners location is the home point + } + + mobile.Location = loc; + mobile.Map = map; + + BaseXmlSpawner.ApplyObjectStringProperties(null, substitutedtypeName, mobile, trigmob, attachedto, out status_str); + } + else + if (o is Item item) + { + BaseXmlSpawner.AddSpawnItem(null, attachedto, TheSpawn, item, loc, map, trigmob, false, substitutedtypeName, out status_str); + } + } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + } + } + + private static void RemoveFromSectorTable(Sector s, XmlSpawner spawner) + { + if (s == null || s.Owner == null || s.Owner == Map.Internal || GlobalSectorTable[s.Owner.MapID] == null) + { + return; + } + + // find the sector + List spawnerlist; + if (GlobalSectorTable[s.Owner.MapID].TryGetValue(s, out spawnerlist) && spawnerlist != null) + { + //List spawnerlist = GlobalSectorTable[s.Owner.MapID][s]; + if (spawnerlist.Contains(spawner)) + { + spawnerlist.Remove(spawner); + } + } + } + + private void ResetSectorList() + { + // remove the global sector entries + if (sectorList != null) + { + foreach (Sector s in sectorList) + { + RemoveFromSectorTable(s, this); + + } + } + sectorList = null; + UseSectorActivate = false; + + // force an update of the sector list + bool sectorrefresh = HasActiveSectors; + } + + public void LoadXmlConfig(string filename) + { + if (filename == null || filename.Length <= 0) + { + return; + } + + // Check if the file exists + if (File.Exists(filename)) + { + FileStream fs = null; + try + { + fs = File.Open(filename, FileMode.Open, FileAccess.Read); + } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + + if (fs == null) + { + status_str = string.Format("Unable to open {0} for loading", filename); + return; + } + + // Create the data set + DataSet ds = new DataSet(XmlDataSetName); + + // Read in the file + bool fileerror = false; + try + { + ds.ReadXml(fs); + } + catch { fileerror = true; } + // close the file + fs.Close(); + if (fileerror) + { + Console.WriteLine("XmlSpawner: Error in XML config file '{0}'", filename); + return; + } + + // Check that at least a single table was loaded + if (ds.Tables.Count > 0) + { + if (ds.Tables[XmlTableName] != null && ds.Tables[XmlTableName].Rows.Count > 0) + { + foreach (DataRow dr in ds.Tables[XmlTableName].Rows) + { + string strEntry = null; + bool boolEntry = true; + double doubleEntry = 0; + int intEntry = 0; + + var valid_entry = true; + try { strEntry = (string)dr["Name"]; } + catch { valid_entry = false; } + if (valid_entry) { Name = strEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["X"]); } + catch { valid_entry = false; } + if (valid_entry) { m_X = intEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["Y"]); } + catch { valid_entry = false; } + if (valid_entry) { m_Y = intEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["Width"]); } + catch { valid_entry = false; } + if (valid_entry) { m_Width = intEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["Height"]); } + catch { valid_entry = false; } + if (valid_entry) { m_Height = intEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["CentreX"]); } + catch { valid_entry = false; } + if (valid_entry) { X = intEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["CentreY"]); } + catch { valid_entry = false; } + if (valid_entry) { Y = intEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["CentreZ"]); } + catch { valid_entry = false; } + if (valid_entry) { Z = intEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["SequentialSpawning"]); } + catch { valid_entry = false; } + if (valid_entry) { m_SequentialSpawning = intEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["ProximityRange"]); } + catch { valid_entry = false; } + if (valid_entry) { m_ProximityRange = intEntry; } + + valid_entry = true; + try { strEntry = (string)dr["ProximityTriggerMessage"]; } + catch { valid_entry = false; } + if (valid_entry) { m_ProximityTriggerMessage = strEntry; } + + valid_entry = true; + try { strEntry = (string)dr["SpeechTrigger"]; } + catch { valid_entry = false; } + if (valid_entry) { m_SpeechTrigger = strEntry; } + + valid_entry = true; + try { strEntry = (string)dr["SkillTrigger"]; } + catch { valid_entry = false; } + if (valid_entry) { m_SkillTrigger = strEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["ProximityTriggerSound"]); } + catch { valid_entry = false; } + if (valid_entry) { m_ProximityTriggerSound = intEntry; } + + valid_entry = true; + try { strEntry = (string)dr["ItemTriggerName"]; } + catch { valid_entry = false; } + if (valid_entry) { m_ItemTriggerName = strEntry; } + + valid_entry = true; + try { strEntry = (string)dr["NoItemTriggerName"]; } + catch { valid_entry = false; } + if (valid_entry) { m_NoItemTriggerName = strEntry; } + + // check for the delayinsec entry + bool delayinsec = false; + try { delayinsec = bool.Parse((string)dr["DelayInSec"]); } + catch { } + + valid_entry = true; + try { doubleEntry = double.Parse((string)dr["MinDelay"]); } + catch { valid_entry = false; } + if (valid_entry) + { + m_MinDelay = delayinsec ? TimeSpan.FromSeconds(doubleEntry) : TimeSpan.FromMinutes(doubleEntry); + } + + valid_entry = true; + try { doubleEntry = double.Parse((string)dr["MaxDelay"]); } + catch { valid_entry = false; } + if (valid_entry) + { + m_MaxDelay = delayinsec ? TimeSpan.FromSeconds(doubleEntry) : TimeSpan.FromMinutes(doubleEntry); + } + + valid_entry = true; + try { doubleEntry = double.Parse((string)dr["Duration"]); } + catch { valid_entry = false; } + if (valid_entry) { m_Duration = TimeSpan.FromMinutes(doubleEntry); } + + valid_entry = true; + try { doubleEntry = double.Parse((string)dr["DespawnTime"]); } + catch { valid_entry = false; } + if (valid_entry) { m_DespawnTime = TimeSpan.FromHours(doubleEntry); } + + valid_entry = true; + try { doubleEntry = double.Parse((string)dr["MinRefractory"]); } + catch { valid_entry = false; } + if (valid_entry) { m_MinRefractory = TimeSpan.FromMinutes(doubleEntry); } + + valid_entry = true; + try { doubleEntry = double.Parse((string)dr["MaxRefractory"]); } + catch { valid_entry = false; } + if (valid_entry) { m_MaxRefractory = TimeSpan.FromMinutes(doubleEntry); } + + valid_entry = true; + try { doubleEntry = double.Parse((string)dr["TODStart"]); } + catch { valid_entry = false; } + if (valid_entry) { m_TODStart = TimeSpan.FromMinutes(doubleEntry); } + + valid_entry = true; + try { doubleEntry = double.Parse((string)dr["TODEnd"]); } + catch { valid_entry = false; } + if (valid_entry) { m_TODEnd = TimeSpan.FromMinutes(doubleEntry); } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["TODMode"]); } + catch { valid_entry = false; } + if (valid_entry) { m_TODMode = (TODModeType)intEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["Amount"]); } + catch { valid_entry = false; } + if (valid_entry) { m_StackAmount = intEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["MaxCount"]); } + catch { valid_entry = false; } + if (valid_entry) { m_Count = intEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["Range"]); } + catch { valid_entry = false; } + if (valid_entry) { m_HomeRange = intEntry; } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["Team"]); } + catch { valid_entry = false; } + if (valid_entry) { m_Team = intEntry; } + + valid_entry = true; + try { strEntry = (string)dr["WayPoint"]; } + catch { valid_entry = false; } + if (valid_entry) { m_WayPoint = GetWaypoint(strEntry); } + + valid_entry = true; + try { intEntry = int.Parse((string)dr["KillReset"]); } + catch { valid_entry = false; } + if (valid_entry) { m_KillReset = intEntry; } + + valid_entry = true; + try { doubleEntry = double.Parse((string)dr["TriggerProbability"]); } + catch { valid_entry = false; } + if (valid_entry) { m_TriggerProbability = doubleEntry; } + + valid_entry = true; + try { boolEntry = bool.Parse((string)dr["ExternalTriggering"]); } + catch { valid_entry = false; } + if (valid_entry) { m_ExternalTriggering = boolEntry; } + + valid_entry = true; + try { boolEntry = bool.Parse((string)dr["IsGroup"]); } + catch { valid_entry = false; } + if (valid_entry) { m_Group = boolEntry; } + + valid_entry = true; + try { boolEntry = bool.Parse((string)dr["IsHomeRangeRelative"]); } + catch { valid_entry = false; } + if (valid_entry) { m_HomeRangeIsRelative = boolEntry; } + + valid_entry = true; + try { boolEntry = bool.Parse((string)dr["AllowGhostTriggering"]); } + catch { valid_entry = false; } + if (valid_entry) { m_AllowGhostTriggering = boolEntry; } + + valid_entry = true; + try { boolEntry = bool.Parse((string)dr["AllowNPCTriggering"]); } + catch { valid_entry = false; } + if (valid_entry) { m_AllowNPCTriggering = boolEntry; } + + valid_entry = true; + try { boolEntry = bool.Parse((string)dr["SpawnOnTrigger"]); } + catch { valid_entry = false; } + if (valid_entry) { m_SpawnOnTrigger = boolEntry; } + + valid_entry = true; + try { boolEntry = bool.Parse((string)dr["SmartSpawning"]); } + catch { valid_entry = false; } + if (valid_entry) { m_SmartSpawning = boolEntry; } + + valid_entry = true; + try { strEntry = (string)dr["RegionName"]; } + catch { valid_entry = false; } + if (valid_entry) + { + RegionName = strEntry; + } + + valid_entry = true; + try { strEntry = (string)dr["PlayerPropertyName"]; } + catch { valid_entry = false; } + if (valid_entry) + { + m_PlayerPropertyName = strEntry; + } + + valid_entry = true; + try { strEntry = (string)dr["MobPropertyName"]; } + catch { valid_entry = false; } + if (valid_entry) + { + m_MobPropertyName = strEntry; + } + + valid_entry = true; + try { strEntry = (string)dr["MobTriggerName"]; } + catch { valid_entry = false; } + if (valid_entry) + { + m_MobTriggerName = strEntry; + } + + valid_entry = true; + try { strEntry = (string)dr["ObjectPropertyName"]; } + catch { valid_entry = false; } + if (valid_entry) + { + m_ObjectPropertyName = strEntry; + } + + valid_entry = true; + try { strEntry = (string)dr["ObjectPropertyItemName"]; } + catch { valid_entry = false; } + if (valid_entry) + { + string[] typeargs = strEntry.Split(",".ToCharArray(), 2); + string typestr = null; + string namestr = strEntry; + + if (typeargs.Length > 1) + { + namestr = typeargs[0]; + typestr = typeargs[1]; + } + m_ObjectPropertyItem = BaseXmlSpawner.FindItemByName(this, namestr, typestr); + } + + valid_entry = true; + try { strEntry = (string)dr["SetPropertyItemName"]; } + catch { valid_entry = false; } + if (valid_entry) + { + string[] typeargs = strEntry.Split(",".ToCharArray(), 2); + string typestr = null; + string namestr = strEntry; + + if (typeargs.Length > 1) + { + namestr = typeargs[0]; + typestr = typeargs[1]; + } + m_SetPropertyItem = BaseXmlSpawner.FindItemByName(this, namestr, typestr); + } + + valid_entry = true; + try { strEntry = (string)dr["Name"]; } + catch { valid_entry = false; } + if (valid_entry) { Name = strEntry; } + + valid_entry = true; + try { strEntry = (string)dr["Map"]; } + catch { valid_entry = false; } + if (valid_entry) + { + // Convert the xml map value to a real map object + try + { + Map = Map.Parse(strEntry); + } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + } + + // try loading the new spawn specifications first + SpawnObject[] Spawns = new SpawnObject[0]; + bool havenew = true; + valid_entry = true; + try { Spawns = SpawnObject.LoadSpawnObjectsFromString2((string)dr["Objects2"]); } + catch { havenew = false; } + if (!havenew) + { + // try loading the new spawn specifications + try { Spawns = SpawnObject.LoadSpawnObjectsFromString((string)dr["Objects"]); } + catch { valid_entry = false; } + // can only have one of these defined + } + if (valid_entry) + { + + // clear existing spawns + RemoveSpawnObjects(); + + // Create the new array of spawned objects + m_SpawnObjects = new List(); + + // Assign the list of objects to spawn + SpawnObjects = Spawns; + } + } + } + } + } + } + + public void ReportStatus() + { + if (PropertyInfoList != null) + { + Console.WriteLine("PropertyInfoList: {0}", PropertyInfoList.Count); + foreach (BaseXmlSpawner.TypeInfo to in PropertyInfoList) + { + Console.WriteLine("\t{0}", to.t); + foreach (PropertyInfo p in to.plist) + { + Console.WriteLine("\t\t{0}", p); + } + } + } + + ShowTagList(this); + int count = 0; + Console.WriteLine("Registered SkillsTotal = {0}", count); + } + +#if (TRACE) + + readonly string setname1 = _traceName[1] = "XmlFind"; + readonly string setname2 = _traceName[2] = "HasSector"; + readonly string setname4 = _traceName[4] = "AttachSpeech"; + readonly string setname5 = _traceName[5] = "HasHold"; + readonly string setname8 = _traceName[8] = "OnTick"; + readonly string setname9 = _traceName[9] = "Defrag"; + readonly string setname10 = _traceName[10] = "Respawn"; + readonly string setname11 = _traceName[11] = "SetProp"; + readonly string setname12 = _traceName[12] = "AttachMovement"; + readonly string setname13 = _traceName[13] = "ActiveSector"; + readonly string setname15 = _traceName[15] = "DistroTick"; + readonly string setname16 = _traceName[16] = "GetScaledFaction"; + readonly string setname17 = _traceName[17] = "FactionOnKill"; + readonly string setname18 = _traceName[18] = "CheckAcquire"; + + + private const int MaxTraces = 20; + private static readonly DateTime[] _traceStart = new DateTime[MaxTraces]; + public static TimeSpan[] _traceTotal = new TimeSpan[MaxTraces]; + public static string[] _traceName = new string[MaxTraces]; + public static int[] _traceCount = new int[MaxTraces]; + private static DateTime _traceStartTime = DateTime.UtcNow; + private static double _startProcessTime; + + public static void _TraceStart(int index) + { + if (index < MaxTraces) + { + _traceStart[index] = DateTime.UtcNow; + //_traceStart[index] = Process.GetCurrentProcess().UserProcessorTime; + } + } + public static void _TraceEnd(int index) + { + if (index < MaxTraces) + { + _traceTotal[index] = _traceTotal[index].Add(DateTime.UtcNow - _traceStart[index]); + //XmlSpawner._traceTotal[index] = XmlSpawner._traceTotal[index].Add(Process.GetCurrentProcess().UserProcessorTime - _traceStart[index]); + _traceCount[index]++; + } + } +#else + public static void _TraceStart(int index) { } + public static void _TraceEnd(int index) { } +#endif + + private bool ValidPlayerTrig(Mobile m) + { + if (m == null || m.Deleted) + { + return false; + } + + return (m.Player || m_AllowNPCTriggering) && m.AccessLevel <= TriggerAccessLevel && (!m.Body.IsGhost && !m_AllowGhostTriggering || m.Body.IsGhost && m_AllowGhostTriggering); + } + + private bool AllowTriggering => m_Running && !m_refractActivated && TODInRange && CanSpawn; + + private void ActivateTrigger() + { + DoTimer(); // reset the timer + + // start the refractory timer to set proximity activated to false, thus enabling another activation + if (m_MaxRefractory > TimeSpan.FromMinutes(0)) + { + int minSeconds = (int)m_MinRefractory.TotalSeconds; + int maxSeconds = (int)m_MaxRefractory.TotalSeconds; + + DoTimer3(TimeSpan.FromSeconds(Utility.RandomMinMax(minSeconds, maxSeconds))); + } + + // if the spawnontrigger flag is set, then spawn immediately + if (m_SpawnOnTrigger) + { + NextSpawn = TimeSpan.Zero; + ResetNextSpawnTimes(); + } + + // reset speech triggering if it was set + m_speechTriggerActivated = false; + } + + public void CheckTriggers(Mobile m, Skill s, bool hasproximity) + { + if (AllowTriggering && !m_proximityActivated) // only proximity trigger when no spawns have already been triggered + { + bool needs_speech_trigger = false; + bool needs_player_trigger = false; + bool has_player_trigger = false; + + m_skipped = false; + + // test for the various triggering options in the order of increasing computational demand. No point checking a high demand test + // if a low demand one has already failed. + + // check for external triggering + if (m_ExternalTriggering && !m_ExternalTrigger) + { + return; + } + + // if speech triggering is set then test for successful activation + if (!string.IsNullOrEmpty(m_SpeechTrigger)) + { + needs_speech_trigger = true; + } + // check to see if we have to continue + if (needs_speech_trigger && !m_speechTriggerActivated) + { + return; + } + + // if player property triggering is set then look for the mob and test properties + if (!string.IsNullOrEmpty(m_PlayerPropertyName)) + { + needs_player_trigger = true; + string status_str; + + if (BaseXmlSpawner.TestMobProperty(this, m, m_PlayerPropertyName, out status_str)) + { + has_player_trigger = true; + } + + if (!string.IsNullOrEmpty(status_str)) + { + this.status_str = status_str; + } + } + + // check to see if we have to continue + if (needs_player_trigger && !has_player_trigger) + { + return; + } + + // if this was called without being proximity triggered then check to see that the non-movement triggers were enabled. + if (!hasproximity && !m_ExternalTriggering) + { + return; + } + + // all of the necessary trigger conditions have been met so go ahead and trigger + // after you make the probability check + + if (Utility.RandomDouble() < m_TriggerProbability) + { + // play a sound indicating the spawner has been triggered + if (m_ProximityTriggerSound > 0 && m != null && !m.Deleted) + { + m.PlaySound(m_ProximityTriggerSound); + } + + // display the trigger message + if (!string.IsNullOrEmpty(m_ProximityTriggerMessage) && m != null && !m.Deleted) + { + m.PublicOverheadMessage(MessageType.Regular, 0x3B2, false, m_ProximityTriggerMessage); + } + + // enable spawning at the next ontick + // this will also start the refractory timer and send the triggering indicators + ProximityActivated = true; + + // keep track of who triggered this + m_mob_who_triggered = m; + } + else + { + m_skipped = true; + + // reset speech triggering if it was set + m_speechTriggerActivated = false; + } + } + } + public bool HandlesOnSkillUse => m_Running && m_SkillTrigger != null && m_SkillTrigger.Length > 0; + + // this is the handler for skill use + public void OnSkillUse(Mobile m, Skill skill, bool success) + { + + if (m_Running && m_ProximityRange >= 0 && ValidPlayerTrig(m) && CanSpawn && !m_refractActivated && TODInRange) + { + + if (!Utility.InRange(m.Location, Location, m_ProximityRange)) + { + return; + } + + m_skillTriggerActivated = false; + + // check the skill trigger conditions, Skillname[+/-][,min,max] + if (m_SkillTrigger != null && skill.SkillName == m_SkillTriggerName && + (m_SkillTriggerMin < 0 || skill.Value >= m_SkillTriggerMin) && + (m_SkillTriggerMax < 0 || skill.Value <= m_SkillTriggerMax) && + (m_SkillTriggerSuccess == 3 || m_SkillTriggerSuccess == 1 && success || m_SkillTriggerSuccess == 2 && !success)) + { + // have a skill trigger so flag it and test it + m_skillTriggerActivated = true; + + CheckTriggers(m, skill, true); + } + } + } + public override bool HandlesOnSpeech => m_Running && !string.IsNullOrEmpty(m_SpeechTrigger); + + public override void OnSpeech(SpeechEventArgs e) + { + if (m_Running && m_ProximityRange >= 0 && ValidPlayerTrig(e.Mobile) && CanSpawn && !m_refractActivated && TODInRange) + { + m_speechTriggerActivated = false; + + if (!Utility.InRange(e.Mobile.Location, Location, m_ProximityRange)) + { + return; + } + + if (m_SpeechTrigger != null && e.Speech.ToLower().IndexOf(m_SpeechTrigger.ToLower()) >= 0) + { + e.Handled = true; + + // found the speech trigger so flag it for testing in the onmovement handler where the other proximity features are tested + m_speechTriggerActivated = true; + + CheckTriggers(e.Mobile, null, true); + } + } + } + + public override bool HandlesOnMovement => m_Running && m_ProximityRange >= 0; + + public void AddToMovementList(Mobile m) + { + // go through the list and check for redundancy + if (m_MovementList == null) + { + m_MovementList = new List(); + } + + // check to see if the movement timer is running + if (m_MovementTimer == null || !m_MovementTimer.Running) + { + DoMovementTimer(TimeSpan.FromSeconds(1)); + } + + bool add = true; + + foreach (MovementInfo moveinfo in m_MovementList) + { + Mobile mtrig = moveinfo.trigMob; + if (mtrig == m) + { + add = false; + break; + } + } + + // wasnt on the list so add it + if (add) + { + + // is the list at max throttling length? + if (m_MovementList.Count > MaxMoveCheck) + { + // replace a random entry in the current list with this one + m_MovementList[Utility.Random(m_MovementList.Count)] = new MovementInfo(m); + } + else + { + + m_MovementList.Add(new MovementInfo(m)); + } + } + } + + public void DoMovementTimer(TimeSpan delay) + { + if (m_MovementTimer != null) + { + m_MovementTimer.Stop(); + } + + m_MovementTimer = new MovementTimer(this, delay); + + m_MovementTimer.Start(); + } + + private class MovementTimer : Timer + { + private readonly XmlSpawner m_Spawner; + + public MovementTimer(XmlSpawner spawner, TimeSpan delay) : base(delay) => m_Spawner = spawner; + + protected override void OnTick() + { + // check everyone on the movement list then clear the list + if (m_Spawner != null && !m_Spawner.Deleted) + { + if (m_Spawner.m_Running && !m_Spawner.m_proximityActivated && !m_Spawner.m_refractActivated && m_Spawner.TODInRange && m_Spawner.CanSpawn) + { + int count = 0; + int maxspeed = 0; + foreach (MovementInfo moveinfo in m_Spawner.m_MovementList) + { + Mobile m = moveinfo.trigMob; + if (m == null) + { + continue; + } + + // additional throttling in here by limiting number of mobs that can be checked in a single ontick + count++; + if (count > MaxMoveCheck) + { + break; + } + + int speed = (int)GetDistance(m.Location, moveinfo.trigLocation); + if (speed > maxspeed) + { + maxspeed = speed; + } + + m_Spawner.CheckTriggers(m, null, true); + } + + m_Spawner.MovingPlayerCount = m_Spawner.m_MovementList.Count; + m_Spawner.FastestPlayerSpeed = maxspeed; + + } + m_Spawner.m_MovementList.Clear(); + } + } + } + + public static double GetDistance(Point3D p1, Point3D p2) + { + int xDelta = p1.X - p2.X; + int yDelta = p1.Y - p2.Y; + + return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); + } + + public override void OnMovement(Mobile m, Point3D oldLocation) + { + if (m_Running && m_ProximityRange >= 0 && ValidPlayerTrig(m) && CanSpawn) + { + // check to see if player is within range of the spawner + if (Parent == null && Utility.InRange(m.Location, Location, m_ProximityRange)) + { + // add some throttling code here. + // add the player to a list that gets cleared every few seconds, checking for redundancy then trigger off of the list instead of off of + // the actual movement stream + + AddToMovementList(m); + } + else + { + // clear any speech triggering + m_speechTriggerActivated = false; + } + } + base.OnMovement(m, oldLocation); + } + + public static bool AssignSettings(string argname, string value) + { + switch (argname) + { + case "XmlSpawnDir": + { + XmlSpawnDir = value; + break; + } + case "DiskAccessLevel": + { + DiskAccessLevel = (AccessLevel)Enum.Parse(typeof(AccessLevel), value, true); + break; + } + case "SmartSpawnAccessLevel": + { + SmartSpawnAccessLevel = (AccessLevel)Enum.Parse(typeof(AccessLevel), value, true); + break; + } + case "defaultTriggerSound": + { + defaultTriggerSound = ConvertToInt(value); + defProximityTriggerSound = defaultTriggerSound; + break; + } + case "BaseItemId": + { + BaseItemId = ConvertToInt(value); + break; + } + case "ShowItemId": + { + ShowItemId = ConvertToInt(value); + break; + } + case "MaxMoveCheck": + { + MaxMoveCheck = ConvertToInt(value); + break; + } + case "defMinDelay": + { + defMinDelay = TimeSpan.FromMinutes(ConvertToInt(value)); + break; + } + case "defMaxDelay": + { + defMaxDelay = TimeSpan.FromMinutes(ConvertToInt(value)); + break; + } + case "defRelativeHome": + { + defRelativeHome = bool.Parse(value); + break; + } + case "defSpawnRange": + { + defSpawnRange = ConvertToInt(value); + break; + } + case "defHomeRange": + { + defHomeRange = ConvertToInt(value); + break; + } + case "BlockKeyword": + { + // parse the keyword list and remove them from the keyword hashtables + string[] keywordlist = value.Split(','); + + if (keywordlist.Length > 0) + { + for (int i = 0; i < keywordlist.Length; i++) + { + BaseXmlSpawner.RemoveKeyword(keywordlist[i]); + } + } + + break; + } + case "BlockCommand": + case "ChangeCommand": + { + // delay processing of these settings until after all commands have been registered in their Initialize methods + Timer.DelayCall(TimeSpan.Zero, DelayedAssignSettings, argname, value); + break; + } + default: + { + return false; + } + } + + return true; + } + + private static void DelayedAssignSettings(string argname, string value) + { + switch (argname) + { + case "BlockCommand": + { + // delay processing of this until after all commands have been registered in their Initialize methods + // parse the command list and remove them from the command hashtables + // the syntax is "commandname, commandname, etc." + string[] keywordlist = value.Split(','); + + if (keywordlist.Length > 0) + { + for (int i = 0; i < keywordlist.Length; i++) + { + string commandname = keywordlist[i].Trim().ToLower(); + try + { + CommandSystem.Entries.Remove(commandname); + } + catch + { + Console.WriteLine("{0}: invalid command {1}", argname, commandname); + } + } + } + break; + } + case "ChangeCommand": + { + // delay processing of this until after all commands have been registered in their Initialize methods + // parse the command list and rehash them into the command hashtables + // the syntax is "oldname:newname[:accesslevel], oldname:newname[:accesslevel], etc." + string[] keywordlist = value.Split(','); + + if (keywordlist.Length > 0) + { + for (int i = 0; i < keywordlist.Length; i++) + { + string[] namelist = keywordlist[i].Split(':'); + if (namelist.Length > 1) + { + string oldname = namelist[0].Trim().ToLower(); + string newname = namelist[1].Trim(); + + if (newname.Length == 0) + { + newname = oldname; + } + + AccessLevel access = AccessLevel.Player; + bool validaccess = false; + if (namelist.Length > 2) + { + // get the new accesslevel + try + { + access = (AccessLevel)Enum.Parse(typeof(AccessLevel), namelist[2].Trim(), true); + validaccess = true; + } + catch + { + Console.WriteLine("{0}: invalid accesslevel {1} for {2}", argname, namelist[2], newname); + } + } + // find the command entry for the old name + CommandEntry e = null; + try + { + e = CommandSystem.Entries[oldname]; + } + catch + { + Console.WriteLine("{0}: invalid command {1}", argname, oldname); + } + if (e != null) + { + if (!validaccess) + { + // use the old accesslevel + access = e.AccessLevel; + } + // remove the old command entry + CommandSystem.Entries.Remove(oldname); + // register the new command using the old handler + CommandSystem.Register(newname, access, e.Handler); + } + + // also look in the targetcommands list and adjust name and accesslevel there + foreach (BaseCommand b in TargetCommands.AllCommands) + { + if (b.Commands != null) + { + for (int j = 0; j < b.Commands.Length; j++) + { + string commandname = b.Commands[j]; + if (commandname.ToLower() == oldname) + { + // modify the basecommand with the new name and access + b.Commands[j] = newname; + if (validaccess) + { + b.AccessLevel = access; + } + + // re-register it in the implementors hashtable + List impls = BaseCommandImplementor.Implementors; + + for (int k = 0; k < impls.Count; ++k) + { + BaseCommandImplementor impl = impls[k]; + + if ((b.Supports & impl.SupportRequirement) != 0) + { + try + { + impl.Commands.Remove(commandname); + } + catch (Exception ex) + { + Diagnostics.ExceptionLogging.LogException(ex); + } + impl.Register(b); + } + } + + break; + } + } + } + } + } + } + } + break; + } + } + } + + public delegate bool AssignSettingsHandler(string argname, string value); + + // load in settings from the xmlspawner2.cfg file in the Data directory + public static void LoadSettings(AssignSettingsHandler settingshandler, string section) + { + // Check if the file exists + string path = Path.Combine(Core.BaseDirectory, "Data/xmlspawner.cfg"); + + if (!File.Exists(path)) + { + return; + } + + Console.WriteLine("Loading {0} configuration", section); + using (StreamReader ip = new StreamReader(path)) + { + string line; + string currentsection = null; + int nsettings = 0; + + while ((line = ip.ReadLine()) != null) + { + line = line.Trim(); + + // skip comments + if (line.Length == 0 || line.StartsWith("#")) + { + continue; + } + + if (line.StartsWith("[")) + { + // parse the section name + string[] args = line.Split("[]".ToCharArray(), 3); + if (args.Length > 2) + { + currentsection = args[1].Trim(); + } + } + + // only process the matching classname section + if (currentsection != section) + { + continue; + } + + string[] split = line.Split('='); + + if (split.Length >= 2) + { + string argname = split[0].Trim(); + string value = split[1].Trim(); + + if (argname.Length == 0 || value.Length == 0) + { + continue; + } + + try + { + if (settingshandler(argname, value)) + { + nsettings++; + } + else + { + Console.WriteLine("'{0}' setting is invalid in section [{1}]", argname, currentsection); + } + } + catch (Exception e) + { + Console.WriteLine("Config error '{0}'='{1}'", argname, value); + Console.WriteLine("Error: {0}", e.Message); + Diagnostics.ExceptionLogging.LogException(e); + } + } + } + + if (nsettings > 0) + { + Console.WriteLine("{0} settings processed", nsettings); + } + } + } + + public static void Initialize() + { + LoadSettings(AssignSettings, "XmlSpawner"); + + // initialize the default waypoint name + WayPoint tmpwaypoint = new WayPoint(); + defwaypointname = tmpwaypoint.Name; + tmpwaypoint.Delete(); + + int count = 0; + int regional = 0; + + foreach (Item item in World.Items.Values) + { + if (item is XmlSpawner spawner) + { + count++; + + if (!string.IsNullOrEmpty(spawner.RegionName)) + { + spawner.RegionName = spawner.RegionName; // invoke set(RegionName) + regional++; + } + + // check for smart spawning and restart timers after deser if needed + // note, HasActiveSectors will recalculate the sector list and UseSectorActivate property + bool recalc_sectors = spawner.HasActiveSectors; + + spawner.RestoreISpawner(); + } + } + + // start the global smartspawning timer + if (SmartSpawningSystemEnabled) + { + DoGlobalSectorTimer(TimeSpan.FromSeconds(1)); + } + + // standard commands + CommandSystem.Register("XmlSpawnerShowAll", AccessLevel.Administrator, ShowSpawnPoints_OnCommand); + CommandSystem.Register("XmlSpawnerHideAll", AccessLevel.Administrator, HideSpawnPoints_OnCommand); + CommandSystem.Register("XmlSpawnerWipe", AccessLevel.Administrator, Wipe_OnCommand); + CommandSystem.Register("XmlSpawnerWipeAll", AccessLevel.Administrator, WipeAll_OnCommand); + CommandSystem.Register("XmlSpawnerLoad", DiskAccessLevel, Load_OnCommand); + CommandSystem.Register("XmlSpawnerSave", DiskAccessLevel, Save_OnCommand); + CommandSystem.Register("XmlSpawnerSaveAll", DiskAccessLevel, SaveAll_OnCommand); + CommandSystem.Register("XmlSpawnerRespawn", AccessLevel.Seer, Respawn_OnCommand); + CommandSystem.Register("XmlSpawnerRespawnAll", AccessLevel.Seer, RespawnAll_OnCommand); + + CommandSystem.Register("XmlShow", AccessLevel.Administrator, ShowSpawnPoints_OnCommand); + CommandSystem.Register("XmlHide", AccessLevel.Administrator, HideSpawnPoints_OnCommand); + CommandSystem.Register("XmlHome", AccessLevel.GameMaster, XmlHome_OnCommand); + CommandSystem.Register("XmlUnLoad", DiskAccessLevel, UnLoad_OnCommand); + CommandSystem.Register("XmlSpawnerUnLoad", DiskAccessLevel, UnLoad_OnCommand); + CommandSystem.Register("XmlLoad", DiskAccessLevel, Load_OnCommand); + CommandSystem.Register("XmlLoadHere", DiskAccessLevel, LoadHere_OnCommand); + CommandSystem.Register("XmlNewLoad", DiskAccessLevel, NewLoad_OnCommand); + CommandSystem.Register("XmlNewLoadHere", DiskAccessLevel, NewLoadHere_OnCommand); + CommandSystem.Register("XmlSave", DiskAccessLevel, Save_OnCommand); + CommandSystem.Register("XmlSaveAll", DiskAccessLevel, SaveAll_OnCommand); + CommandSystem.Register("XmlSaveOld", DiskAccessLevel, SaveOld_OnCommand); + CommandSystem.Register("XmlImportSpawners", DiskAccessLevel, XmlImportSpawners_OnCommand); + CommandSystem.Register("XmlImportMSF", DiskAccessLevel, XmlImportMSF_OnCommand); + CommandSystem.Register("XmlImportMap", DiskAccessLevel, XmlImportMap_OnCommand); + CommandSystem.Register("XmlDefaults", AccessLevel.Administrator, XmlDefaults_OnCommand); + CommandSystem.Register("XmlGet", AccessLevel.GameMaster, XmlGetValue_OnCommand); + CommandSystem.Register("OptimalSmartSpawning", AccessLevel.Administrator, OptimalSmartSpawning_OnCommand); + CommandSystem.Register("SmartStat", AccessLevel.GameMaster, SmartStat_OnCommand); + CommandSystem.Register("XmlGo", AccessLevel.GameMaster, SpawnEditorGo_OnCommand); + + TargetCommands.Register(new XmlSetCommand()); + TargetCommands.Register(new XmlSaveSingle()); + +#if (TRACE) + CommandSystem.Register("XmlMake", AccessLevel.Administrator, XmlMake_OnCommand); + CommandSystem.Register("XmlTrace", AccessLevel.Administrator, XmlTrace_OnCommand); + CommandSystem.Register("XmlResetTrace", AccessLevel.Administrator, XmlResetTrace_OnCommand); +#endif + + } + + [Usage("XmlGet property")] + [Description("Returns value of the property on the targeted object.")] + public static void XmlGetValue_OnCommand(CommandEventArgs e) + { + e.Mobile.Target = new GetValueTarget(e); + } + private class GetValueTarget : Target + { + private readonly CommandEventArgs m_e; + public GetValueTarget(CommandEventArgs e) + : base(30, false, TargetFlags.None) => + m_e = e; + + protected override void OnTarget(Mobile from, object targeted) + { + string pname = m_e.GetString(0); + Type ptype; + string result = BaseXmlSpawner.GetPropertyValue(null, targeted, pname, out ptype); + + // see if it was successful + if (ptype == null) + { + return; + } + from.SendMessage("{0}", result); + + } + } + + public class XmlSetCommand : BaseCommand + { + public XmlSetCommand() + { + AccessLevel = AccessLevel.Administrator; + Supports = CommandSupport.All; + Commands = new[] { "XmlSet" }; + ObjectTypes = ObjectTypes.Both; + Usage = "XmlSet "; + Description = "Sets a property value by name of a targeted object. Provides access to all public properties."; + } + + public override void Execute(CommandEventArgs e, object obj) + { + if (e.Length >= 2) + { + string result = BaseXmlSpawner.SetPropertyValue(null, obj, e.GetString(0), e.GetString(1)); + + if (result == "Property has been set.") + { + AddResponse(result); + } + else + { + LogFailure(result); + } + } + else + { + LogFailure("Format: XmlSet "); + } + } + } + + [Usage("TagList property")] + [Description("Lists the keyword taglist for a spawner")] + public static void ShowTagList_OnCommand(CommandEventArgs e) + { + e.Mobile.Target = new TagListTarget(e); + } + + private class TagListTarget : Target + { + private readonly CommandEventArgs m_e; + + public TagListTarget(CommandEventArgs e) + : base(30, false, TargetFlags.None) => + m_e = e; + + protected override void OnTarget(Mobile from, object targeted) + { + if (targeted is XmlSpawner spawner) + { + spawner.ShowTagList(spawner); + } + } + } + + public void ShowTagList(XmlSpawner spawner) + { + int count = 0; + Console.WriteLine("{0} tags", spawner.m_KeywordTagList.Count); + foreach (BaseXmlSpawner.KeywordTag tag in spawner.m_KeywordTagList) + { + count++; + Console.WriteLine("tag {0} : {1}", count, BaseXmlSpawner.TagInfo(tag)); + } + } + + + // added in targeting for the [xmlhome command + private class XmlHomeTarget : Target + { + private readonly CommandEventArgs m_e; + public XmlHomeTarget(CommandEventArgs e) + : base(30, false, TargetFlags.None) => + m_e = e; + + protected override void OnTarget(Mobile from, object targeted) + { + XmlSpawner spawner = null; + + if (targeted is XmlSpawner xmlSpawner) + { + if (m_e.GetString(0) == "status") + { + xmlSpawner.ReportStatus(); + return; + } + } + + + if (targeted is Mobile mobile) + { + spawner = mobile.Spawner as XmlSpawner; + } + else + if (targeted is Item item) + { + spawner = item.Spawner as XmlSpawner; + } + + if (spawner == null) + { + from.SendMessage("Unable to find spawner for this object"); + return; + } + + // check to make sure it is still on the spawner + foreach (SpawnObject so in spawner.m_SpawnObjects) + { + for (int x = 0; x < so.SpawnedObjects.Count; x++) + { + object o = so.SpawnedObjects[x]; + + if (o == targeted) + { + from.SendMessage("{0}, {1}, {2}", spawner.X, spawner.Y, spawner.Z); + + if (m_e.GetString(0) == "go") + { + // make sure the spawner is not in a container. + if (spawner.Parent == null) + { + from.Location = new Point3D(spawner.Location); + from.Map = spawner.Map; + } + else + { + from.SendMessage("Spawner is in a container"); + } + } + else + if (m_e.GetString(0) == "send") + { + // make sure the spawner is not in a container. + if (spawner.Parent == null) + { + if (o is Item item) + { + item.Location = new Point3D(spawner.Location); + item.Map = spawner.Map; + } + if (o is Mobile mobile1) + { + mobile1.Location = new Point3D(spawner.Location); + mobile1.Map = spawner.Map; + } + } + else + { + from.SendMessage("Spawner is in a container"); + } + } + else if (m_e.GetString(0) == "gump") + { + spawner.OnDoubleClick(from); + } + + return; + } + } + } + } + } + + [Usage("XmlHome [go][gump][send]")] + [Description("Returns the coordinates of the spawner for the targeted object. Args: 'go' teleports to spawner, 'gump' opens spawner gump, 'send' sends mob home")] + public static void XmlHome_OnCommand(CommandEventArgs e) + { + e.Mobile.Target = new XmlHomeTarget(e); + } + + private static void XmlSaveDefaults(string filePath, Mobile m) + { + + if (string.IsNullOrEmpty(filePath)) + { + return; + } + + using (StreamWriter op = new StreamWriter(filePath)) + { + XmlTextWriter xml = new XmlTextWriter(op) + { + Formatting = Formatting.Indented, + IndentChar = '\t', + Indentation = 1 + }; + + xml.WriteStartDocument(true); + + xml.WriteStartElement("XmlDefaults"); + + xml.WriteStartElement("defProximityRange"); + xml.WriteString(defProximityRange.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defTriggerProbability"); + xml.WriteString(defTriggerProbability.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defProximityTriggerSound"); + xml.WriteString(defProximityTriggerSound.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defMinRefractory"); + xml.WriteString(defMinRefractory.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defMaxRefractory"); + xml.WriteString(defMaxRefractory.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defTODStart"); + xml.WriteString(defTODStart.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defTODEnd"); + xml.WriteString(defTODEnd.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defStackAmount"); + xml.WriteString(defAmount.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defDuration"); + xml.WriteString(defDuration.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defIsGroup"); + xml.WriteString(defIsGroup.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defTeam"); + xml.WriteString(defTeam.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defRelativeHome"); + xml.WriteString(defRelativeHome.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defSpawnRange"); + xml.WriteString(defSpawnRange.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defHomeRange"); + xml.WriteString(defHomeRange.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defMinDelay"); + xml.WriteString(defMinDelay.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defMaxDelay"); + xml.WriteString(defMaxDelay.ToString()); + xml.WriteEndElement(); + xml.WriteStartElement("defTODMode"); + xml.WriteString(defTODMode.ToString()); + xml.WriteEndElement(); + + xml.WriteEndElement(); + + xml.Close(); + } + m.SendMessage("defaults saved to {0}", filePath); + } + + public static void XmlLoadDefaults(string filePath, Mobile m) + { + if (m == null || m.Deleted) + { + return; + } + + if (!string.IsNullOrEmpty(filePath)) + { + + if (File.Exists(filePath)) + { + XmlDocument doc = new XmlDocument(); + doc.Load(filePath); + + XmlElement root = doc["XmlDefaults"]; + LoadDefaults(root); + m.SendMessage("defaults loaded successfully from {0}", filePath); + } + else + { + m.SendMessage("File {0} does not exist.", filePath); + } + } + } + + private static void LoadDefaults(XmlElement node) + { + + try { defProximityRange = int.Parse(node["defProximityRange"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defTriggerProbability = double.Parse(node["defTriggerProbability"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defProximityTriggerSound = int.Parse(node["defProximityTriggerSound"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defMinRefractory = TimeSpan.Parse(node["defMinRefractory"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defMaxRefractory = TimeSpan.Parse(node["defMaxRefractory"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defTODStart = TimeSpan.Parse(node["defTODStart"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defTODEnd = TimeSpan.Parse(node["defTODEnd"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defAmount = int.Parse(node["defStackAmount"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defDuration = TimeSpan.Parse(node["defDuration"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defIsGroup = bool.Parse(node["defIsGroup"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defTeam = int.Parse(node["defTeam"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defRelativeHome = bool.Parse(node["defRelativeHome"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defSpawnRange = int.Parse(node["defSpawnRange"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defHomeRange = int.Parse(node["defHomeRange"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defMinDelay = TimeSpan.Parse(node["defMinDelay"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + try { defMaxDelay = TimeSpan.Parse(node["defMaxDelay"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + int todmode = 0; + try { todmode = int.Parse(node["defTODMode"].InnerText); } + catch (Exception e) + { + Diagnostics.ExceptionLogging.LogException(e); + } + switch (todmode) + { + case (int)TODModeType.Realtime: + { + defTODMode = TODModeType.Realtime; + break; + } + case (int)TODModeType.Gametime: + { + defTODMode = TODModeType.Gametime; + break; + } + } + } + + [Usage("XmlDefaults [defaultpropertyname value]")] + [Description("Returns or changes the default settings of the spawner.")] + public static void XmlDefaults_OnCommand(CommandEventArgs e) + { + Mobile m = e.Mobile; + if (m == null || m.Deleted) + { + return; + } + + if (e.Arguments.Length >= 1) + { + // leave open the possibility of just requesting display of a single property + if (e.Arguments.Length == 2) + { + if (e.Arguments[0].ToLower() == "save") + { + XmlSaveDefaults(e.Arguments[1], m); + } + else if (e.Arguments[0].ToLower() == "load") + { + XmlLoadDefaults(e.Arguments[1], m); + } + else if (e.Arguments[0].ToLower() == "maxdelay") + { + try + { + defMaxDelay = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1])); + m.SendMessage("MaxDelay = {0}", defMaxDelay); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "mindelay") + { + try + { + defMinDelay = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1])); + m.SendMessage("MinDelay = {0}", defMinDelay); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "spawnrange") + { + try + { + defSpawnRange = Convert.ToInt32(e.Arguments[1]); + m.SendMessage("SpawnRange = {0}", defSpawnRange); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "homerange") + { + try + { + defHomeRange = Convert.ToInt32(e.Arguments[1]); + m.SendMessage("HomeRange = {0}", defHomeRange); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "relativehome") + { + try + { + defRelativeHome = Convert.ToBoolean(e.Arguments[1]); + m.SendMessage("RelativeHome = {0}", defRelativeHome); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "proximitytriggersound") + { + try + { + defProximityTriggerSound = Convert.ToInt32(e.Arguments[1]); + m.SendMessage("ProximityTriggerSound = {0}", defProximityTriggerSound); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "proximityrange") + { + try + { + defProximityRange = Convert.ToInt32(e.Arguments[1]); + m.SendMessage("ProximityRange = {0}", defProximityRange); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "triggerprobability") + { + try + { + defTriggerProbability = Convert.ToDouble(e.Arguments[1]); + m.SendMessage("TriggerProbability = {0}", defTriggerProbability); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "todstart") + { + try + { + defTODStart = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1])); + m.SendMessage("TODStart = {0}", defTODStart); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "todend") + { + try + { + defTODEnd = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1])); + m.SendMessage("TODEnd = {0}", defTODEnd); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "stackamount") + { + try + { + defAmount = Convert.ToInt32(e.Arguments[1]); + m.SendMessage("StackAmount = {0}", defAmount); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "duration") + { + try + { + defDuration = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1])); + m.SendMessage("Duration = {0}", defDuration); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "group") + { + try + { + defIsGroup = Convert.ToBoolean(e.Arguments[1]); + m.SendMessage("Group = {0}", defIsGroup); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "team") + { + try + { + defTeam = Convert.ToInt32(e.Arguments[1]); + m.SendMessage("Team = {0}", defTeam); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "todmode") + { + try + { + int todmode = Convert.ToInt32(e.Arguments[1]); + switch (todmode) + { + case (int)TODModeType.Gametime: + { + defTODMode = TODModeType.Gametime; + break; + } + case (int)TODModeType.Realtime: + { + defTODMode = TODModeType.Realtime; + break; + } + } + m.SendMessage("TODMode = {0}", defTODMode); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "maxrefractory") + { + try + { + defMaxRefractory = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1])); + m.SendMessage("MaxRefractory = {0}", defMaxRefractory); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else if (e.Arguments[0].ToLower() == "minrefractory") + { + try + { + defMinRefractory = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1])); + m.SendMessage("MinRefractory = {0}", defMinRefractory); + } + catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); } + } + else + { + m.SendMessage("{0} : no such default value.", e.Arguments[0]); + } + } + + } + else + { + // just display the values + m.SendMessage("TriggerProbability = {0}", defTriggerProbability); + m.SendMessage("ProximityRange = {0}", defProximityRange); + m.SendMessage("ProximityTriggerSound = {0}", defProximityTriggerSound); + m.SendMessage("MinRefractory = {0}", defMinRefractory); + m.SendMessage("MaxRefractory = {0}", defMaxRefractory); + m.SendMessage("TODStart = {0}", defTODStart); + m.SendMessage("TODEnd = {0}", defTODEnd); + m.SendMessage("TODMode = {0}", defTODMode); + m.SendMessage("StackAmount = {0}", defAmount); + m.SendMessage("Duration = {0}", defDuration); + m.SendMessage("Group = {0}", defIsGroup); + m.SendMessage("Team = {0}", defTeam); + m.SendMessage("RelativeHome = {0}", defRelativeHome); + m.SendMessage("SpawnRange = {0}", defSpawnRange); + m.SendMessage("HomeRange = {0}", defHomeRange); + m.SendMessage("MinDelay = {0}", defMinDelay); + m.SendMessage("MaxDelay = {0}", defMaxDelay); + } + } + + [Usage("XmlSpawnerShowAll")] + [Aliases("XmlShow")] + [Description("Makes all XmlSpawner objects movable and also changes the item id to a blue ships mast for easy identification.")] + public static void ShowSpawnPoints_OnCommand(CommandEventArgs e) + { + List ToShow = new List(); + foreach (Item item in World.Items.Values) + { + if (item is XmlSpawner) + { + //turned off visibility. Admins will still see masts but players will not. + item.Visible = false; // set the spawn item visibility + item.Movable = false; // Make the spawn item movable + item.Hue = 88; // Bright blue colour so its easy to spot + item.ItemID = ShowItemId; // Ship Mast (Very tall, easy to see if beneath other objects) + + // find container-held spawners to be marked with an external static + if (item.Parent != null && item.RootParent is Container) + { + ToShow.Add(item); + } + } + } + + // place the statics + foreach (XmlSpawner xml_item in ToShow) + { + // does the spawner already have a static attached to it? could happen if two showall commands are issued in a row. + // if so then dont add another + if ((xml_item.m_ShowContainerStatic == null || xml_item.m_ShowContainerStatic.Deleted) && xml_item.RootParent is Container rootItem) + { + // calculate a world location for the static. Position it just above the container + int x = rootItem.Location.X; + int y = rootItem.Location.Y; + int z = rootItem.Location.Z + 10; + + Static s = new Static(ShowItemId) + { + Visible = false + }; + s.MoveToWorld(new Point3D(x, y, z), rootItem.Map); + + xml_item.m_ShowContainerStatic = s; + } + + } + } + + [Usage("XmlSpawnerHideAll")] + [Aliases("XmlHide")] + [Description("Makes all XmlSpawner objects invisible and unmovable returns the object id to the default.")] + public static void HideSpawnPoints_OnCommand(CommandEventArgs e) + { + List ToDelete = new List(); + foreach (Item item in World.Items.Values) + { + if (item is XmlSpawner xmlItem) + { + xmlItem.Visible = false; + xmlItem.Movable = false; + xmlItem.Hue = 0; + xmlItem.ItemID = BaseItemId; + + // get rid of the external static marker for container-held spawners + // check anything that might have been tagged with a container static + if (xmlItem.m_ShowContainerStatic != null && !xmlItem.m_ShowContainerStatic.Deleted) + { + ToDelete.Add(xmlItem); + } + } + } + foreach (XmlSpawner xml_item in ToDelete) + { + if (xml_item.m_ShowContainerStatic != null && !xml_item.m_ShowContainerStatic.Deleted) + { + xml_item.m_ShowContainerStatic.Delete(); + } + } + } + + [Usage("XmlGo | [z]")] + [Description("Go command used with spawn editor, takes the name of the map as the first parameter.")] + private static void SpawnEditorGo_OnCommand(CommandEventArgs e) + { + if (e == null) + { + return; + } + + Mobile from = e.Mobile; + + // Make sure a map name was given at least + if (from != null && e.Length >= 1) + { + string MapName = e.Arguments[0]; + + // Get the map + Map NewMap; + // Convert the xml map value to a real map object + if (string.Compare(MapName, Map.Trammel.Name, true) == 0) + { + NewMap = Map.Trammel; + } + else if (string.Compare(MapName, Map.Felucca.Name, true) == 0) + { + NewMap = Map.Felucca; + } + else if (string.Compare(MapName, Map.Ilshenar.Name, true) == 0) + { + NewMap = Map.Ilshenar; + } + else if (string.Compare(MapName, Map.Malas.Name, true) == 0) + { + NewMap = Map.Malas; + } + else if (string.Compare(MapName, Map.Tokuno.Name, true) == 0) + { + NewMap = Map.Tokuno; + } + else + { + from.SendMessage("Map '{0}' does not exist!", MapName); + return; + } + + // Now that the map has been determined, continue + // Check if the request is to simply change maps + if (e.Length == 1) + { + // Map Change ONLY + from.Map = NewMap; + } + else if (e.Length == 3) + { + // Map & X Y ONLY + if (NewMap != null) + { + int x = e.GetInt32(1); + int y = e.GetInt32(2); + int z = NewMap.GetAverageZ(x, y); + from.Map = NewMap; + from.Location = new Point3D(x, y, z); + } + } + else if (e.Length == 4) + { + // Map & X Y Z + from.Map = NewMap; + from.Location = new Point3D(e.GetInt32(1), e.GetInt32(2), e.GetInt32(3)); + } + else + { + from.SendMessage("Format: XmlGo | [z]"); + } + } + } + + [Usage("SmartStat [accesslevel Player/Counselor/GameMaster/Seer/Administrator]")] + [Description("Returns the spawn reduction due to SmartSpawning.")] + public static void SmartStat_OnCommand(CommandEventArgs e) + { + if (e == null || e.Mobile == null) + { + return; + } + + if (e.Arguments.Length > 1 && e.Arguments[0].ToLower() == "accesslevel" && e.Mobile.AccessLevel >= AccessLevel.Administrator) + { + try + { + SmartSpawnAccessLevel = (AccessLevel)Enum.Parse(typeof(AccessLevel), e.Arguments[1], true); + } + catch (Exception ex) { Diagnostics.ExceptionLogging.LogException(ex); } + } + // handle the + // number of spawners + int count = 0; + // number of actual spawns + int currentcount = 0; + int smartcount = 0; + int inactivecount = 0; + // maximum possible spawns + int totalcount = 0; + int maxcount = 0; + // maximum possible of spawns that are currently inactivated + int savings = 0; + foreach (Item item in World.Items.Values) + { + if (item is XmlSpawner spawner) + { + if (spawner.Deleted) + { + continue; + } + + totalcount += spawner.MaxCount; + // get the current count without defragging + currentcount += spawner.SafeCurrentCount; + count++; + + // check to see if smartspawning is set + if (spawner.SmartSpawning) + { + smartcount++; + maxcount += spawner.MaxCount; + } + + if (spawner.IsInactivated) + { + inactivecount++; + savings += spawner.MaxCount; + } + } + } + + int percent = 0; + + int maxpercent = 0; + if (totalcount > 0) + { + percent = 100 * savings / totalcount; + maxpercent = 100 * maxcount / totalcount; + } + + e.Mobile.SendMessage( + "Smartspawning access level is {10}\n" + + "--------------------------------\n" + + "{0} XmlSpawners\n" + + "{1} are configured for SmartSpawning\n" + + "{2} are currently inactivated\n" + + "{9} sectors being monitored\n" + + "Maximum possible spawn count is {3}\n" + + "Maximum possible spawn reduction is {4}\n" + + "Current spawn count is {5}\n" + + "Current spawn reduction is {6}\n" + + "Maximum possible savings is {7}%\n" + + "Current savings is {8}%", + count, smartcount, inactivecount, totalcount, maxcount, currentcount, savings, maxpercent, + percent, totalSectorsMonitored, SmartSpawnAccessLevel); + } + + [Usage("OptimalSmartSpawning [max spawn/homerange diff]")] + [Description("Activates SmartSpawning on XmlSpawners that are well-suited for use of this feature.")] + public static void OptimalSmartSpawning_OnCommand(CommandEventArgs e) + { + int maxdiff = 1; + if (e.Arguments.Length > 0) + { + try + { + maxdiff = int.Parse(e.Arguments[0]); + } + catch (Exception ex) + { + Diagnostics.ExceptionLogging.LogException(ex); + } + } + int count = 0; + int maxcount = 0; + foreach (Item item in World.Items.Values) + { + if (item is XmlSpawner spawner) + { + // determine whether this spawner is a good candidate + + if (spawner.Deleted) + { + continue; + } + + // ignore spawners in towns + //if (Region.Find(spawner.Location, spawner.Map) is Regions.TownRegion) continue; + + // dont bother setting it on triggered spawners + if (spawner.ProximityRange >= 0) + { + continue; + } + + // check the relative spawnrange and homerange. Dont set it on spawners with a larger homerange than spawnrange + int width = spawner.m_Width; + int height = spawner.m_Height; + + if (spawner.HomeRange * 2 > width + maxdiff * 2 || spawner.HomeRange * 2 > height + maxdiff * 2 && spawner.m_Region != null) + { + continue; + } + + int nso = 0; + + if (spawner.m_SpawnObjects != null) + { + nso = spawner.m_SpawnObjects.Count; + } + + // empty spawner so skip it + if (nso == 0) + { + continue; + } + + bool skipit = false; + + // check the spawn types + for (int i = 0; i < nso; ++i) + { + SpawnObject so = spawner.m_SpawnObjects[i]; + + if (so == null) + { + continue; + } + + string typestr = so.TypeName; + + Type type = AssemblyHandler.FindTypeByName(typestr); + + // if it has basevendors on it or invalid types, then skip it + if (typestr == null || type != null && (type == typeof(BaseVendor) || type.IsSubclassOf(typeof(BaseVendor))) || + type == null && !BaseXmlSpawner.IsTypeOrItemKeyword(typestr) && typestr.IndexOf('{') == -1 && !typestr.StartsWith("*") && !typestr.StartsWith("#")) + { + skipit = true; + break; + } + } + + if (!skipit) + { + count++; + spawner.SmartSpawning = true; + maxcount += spawner.MaxCount; + } + } + } + + e.Mobile.SendMessage("Configured {0} XmlSpawners for SmartSpawning using maxdiff of {1}", count, maxdiff); + e.Mobile.SendMessage("Estimated item/mob reduction is {0}", maxcount); + } + + [Usage("XmlSpawnerWipe [SpawnerPrefixFilter]")] + [Description("Removes all XmlSpawner objects from the current map.")] + public static void Wipe_OnCommand(CommandEventArgs e) + { + WipeSpawners(e, false); + } + + [Usage("XmlSpawnerWipeAll [SpawnerPrefixFilter]")] + [Description("Removes all XmlSpawner objects from the entire world.")] + public static void WipeAll_OnCommand(CommandEventArgs e) + { + WipeSpawners(e, true); + } + + public static void XmlUnLoadFromFile(string filename, string SpawnerPrefix, Mobile from, out int processedmaps, out int processedspawners) + { + + processedmaps = 0; + processedspawners = 0; + if (filename == null || filename.Length <= 0) + { + return; + } + + int total_processed_maps = 0; + int total_processed_spawners = 0; + + // Check if the file exists + if (File.Exists(filename)) + { + FileStream fs = null; + try + { + fs = File.Open(filename, FileMode.Open, FileAccess.Read); + } + catch { } + + if (fs == null) + { + if (from != null) + { + from.SendMessage("Unable to open {0} for unloading", filename); + } + + return; + } + + XmlUnLoadFromStream(fs, filename, SpawnerPrefix, from, out processedmaps, out processedspawners); + + } + else + // check to see if it is a directory + if (Directory.Exists(filename)) + { + // if so then import all of the .xml files in the directory + string[] files = null; + try + { + files = Directory.GetFiles(filename, "*.xml"); + } + catch { } + if (files != null && files.Length > 0) + { + if (from != null) + { + from.SendMessage("UnLoading {0} .xml files from directory {1}", files.Length, filename); + } + + foreach (string file in files) + { + XmlUnLoadFromFile(file, SpawnerPrefix, from, out processedmaps, out processedspawners); + total_processed_maps += processedmaps; + total_processed_spawners += processedspawners; + } + } + // recursively search subdirectories for more .xml files + string[] dirs = null; + try + { + dirs = Directory.GetDirectories(filename); + } + catch { } + if (dirs != null && dirs.Length > 0) + { + foreach (string dir in dirs) + { + XmlUnLoadFromFile(dir, SpawnerPrefix, from, out processedmaps, out processedspawners); + total_processed_maps += processedmaps; + total_processed_spawners += processedspawners; + } + } + if (from != null) + { + from.SendMessage("UnLoaded a total of {0} .xml files and {2} spawners from directory {1}", total_processed_maps, filename, total_processed_spawners); + } + + processedmaps = total_processed_maps; + processedspawners = total_processed_spawners; + } + else + { + if (from != null) + { + from.SendMessage("{0} does not exist", filename); + } + } + + } + + public static void XmlUnLoadFromStream(Stream fs, string filename, string SpawnerPrefix, Mobile from, out int processedmaps, out int processedspawners) + { + processedmaps = 0; + processedspawners = 0; + + if (fs == null) + { + return; + } + + int TotalCount = 0; + int TrammelCount = 0; + int FeluccaCount = 0; + int IlshenarCount = 0; + int MalasCount = 0; + int TokunoCount = 0; + int OtherCount = 0; + int bad_spawner_count = 0; + int spawners_deleted = 0; + + if (from != null) + { + from.SendMessage(string.Format("UnLoading {0} objects{1} from file {2}.", + "XmlSpawner", !string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" : string.Empty, filename)); + } + + // Create the data set + DataSet ds = new DataSet(SpawnDataSetName); + + // Read in the file + //ds.ReadXml(e.Arguments[0].ToString()); + bool fileerror = false; + try + { + ds.ReadXml(fs); + } + catch + { + if (from != null) + { + from.SendMessage(33, "Error reading xml file {0}", filename); + } + + fileerror = true; + } + // close the file + fs.Close(); + if (fileerror) + { + return; + } + + // Check that at least a single table was loaded + if (ds.Tables.Count > 0) + { + // Add each spawn point to the current map + if (ds.Tables[SpawnTablePointName] != null && ds.Tables[SpawnTablePointName].Rows.Count > 0) + { + foreach (DataRow dr in ds.Tables[SpawnTablePointName].Rows) + { + // load in the spawner info. Certain fields are required and therefore cannot be ignored + // the exception handler for those will flag bad_spawner and the result will be logged + + // Each row makes up a single spawner + string SpawnName = "Spawner"; + try { SpawnName = (string)dr["Name"]; } + catch { } + + // Check if there is any spawner name criteria specified on the unload + if (SpawnerPrefix == null || SpawnerPrefix.Length == 0 || SpawnName.StartsWith(SpawnerPrefix)) + { + bool bad_spawner = false; + // Try load the GUID (might not work so create a new GUID) + Guid SpawnId = Guid.NewGuid(); + try { SpawnId = new Guid((string)dr["UniqueId"]); } + catch { bad_spawner = true; } + // have to have a GUID or no point in continuing + if (bad_spawner) + { + bad_spawner_count++; + continue; + } + // Get the map (default to the mobiles map) + Map SpawnMap = Map.Internal; + string XmlMapName = SpawnMap.Name; + + // Try to get the "map" field, but in case it doesn't exist, catch and discard the exception + try { XmlMapName = (string)dr["Map"]; } + catch { } + + // Convert the xml map value to a real map object + if (string.Compare(XmlMapName, Map.Trammel.Name, true) == 0 || XmlMapName == "Trammel") + { + SpawnMap = Map.Trammel; + TrammelCount++; + } + else if (string.Compare(XmlMapName, Map.Felucca.Name, true) == 0 || XmlMapName == "Felucca") + { + SpawnMap = Map.Felucca; + FeluccaCount++; + } + else if (string.Compare(XmlMapName, Map.Ilshenar.Name, true) == 0 || XmlMapName == "Ilshenar") + { + SpawnMap = Map.Ilshenar; + IlshenarCount++; + } + else if (string.Compare(XmlMapName, Map.Malas.Name, true) == 0 || XmlMapName == "Malas") + { + SpawnMap = Map.Malas; + MalasCount++; + } + else if (string.Compare(XmlMapName, Map.Tokuno.Name, true) == 0 || XmlMapName == "Tokuno") + { + SpawnMap = Map.Tokuno; + TokunoCount++; + } + else + { + try + { + SpawnMap = Map.Parse(XmlMapName); + } + catch { } + OtherCount++; + } + + // Check if this spawner already exists + XmlSpawner OldSpawner = null; + foreach (Item i in World.Items.Values) + { + if (i is XmlSpawner checkXmlSpawner) + { + // Check if the spawners GUID is the same as the one being unloaded + // and that the spawners map is the same as the one being unloaded + if (checkXmlSpawner.UniqueId == SpawnId.ToString() + /*&& (CheckXmlSpawner.Map == SpawnMap)*/) + { + OldSpawner = checkXmlSpawner; + if (OldSpawner != null) + { + spawners_deleted++; + OldSpawner.Delete(); + } + + break; + } + } + } + } + + TotalCount++; + } + } + } + + try + { + fs.Close(); + } + catch { } + + if (from != null) + { + from.SendMessage("{0}/{8} spawner(s) were unloaded using file {1} [Trammel={2}, Felucca={3}, Ilshenar={4}, Malas={5}, Tokuno={6}, Other={7}].", + spawners_deleted, filename, TrammelCount, FeluccaCount, IlshenarCount, MalasCount, TokunoCount, OtherCount, TotalCount); + } + + if (bad_spawner_count > 0) + { + if (from != null) + { + from.SendMessage(33, "{0} bad spawners detected.", bad_spawner_count); + } + } + + processedmaps = 1; + processedspawners = TotalCount; + } + + [Usage("XmlSpawnerUnLoad [SpawnerPrefixFilter]")] + [Aliases("XmlUnload")] + [Description("UnLoads XmlSpawner objects from the proper map as defined in the file supplied.")] + public static void UnLoad_OnCommand(CommandEventArgs e) + { + if (e.Mobile.AccessLevel >= DiskAccessLevel) + { + if (e.Arguments.Length >= 1) + { + // Spawner unload criteria (if any) + string SpawnerPrefix = string.Empty; + + // Check if there is an argument provided (load criteria) + if (e.Arguments.Length > 1) + { + SpawnerPrefix = e.Arguments[1]; + } + + string filename = LocateFile(e.Arguments[0]); + int processedmaps; + int processedspawners; + XmlUnLoadFromFile(filename, SpawnerPrefix, e.Mobile, out processedmaps, out processedspawners); + } + else + { + e.Mobile.SendMessage("Usage: {0} ", e.Command); + } + } + else + { + e.Mobile.SendMessage("You do not have rights to perform this command."); + } + } + + [Usage("XmlImportMap ")] + [Description("Loads spawner definitions from a .map file")] + public static void XmlImportMap_OnCommand(CommandEventArgs e) + { + if (e.Mobile.AccessLevel >= DiskAccessLevel) + { + if (e.Arguments.Length >= 1) + { + string filename = e.Arguments[0]; + + int processedmaps; + int processedspawners; + XmlImportMap(filename, e.Mobile, out processedmaps, out processedspawners); + } + else + { + e.Mobile.SendMessage("Usage: {0} ", e.Command); + } + } + else + { + e.Mobile.SendMessage("You do not have rights to perform this command."); + } + } + + public static void XmlImportMap(string filename, Mobile from, out int processedmaps, out int processedspawners) + { + processedmaps = 0; + processedspawners = 0; + int total_processed_maps = 0; + int total_processed_spawners = 0; + if (filename == null || filename.Length <= 0 || from == null || from.Deleted) + { + return; + } + + // Check if the file exists + if (File.Exists(filename)) + { + int spawnercount = 0; + int badspawnercount = 0; + int linenumber = 0; + // default is no map override, use the map spec from each spawn line + int overridemap = -1; + double overridemintime = -1; + double overridemaxtime = -1; + bool newformat = false; + try + { + // Create an instance of StreamReader to read from a file. + // The using statement also closes the StreamReader. + using (StreamReader sr = new StreamReader(filename)) + { + string line; + // Read and display lines from the file until the end of + // the file is reached. + while ((line = sr.ReadLine()) != null) + { + // the old format of each .map line is * Dragon:Wyvern 5209 965 -40 2 2 10 50 30 1 + // * typename:typename:... x y z map mindelay maxdelay homerange spawnrange maxcount + // * | typename:typename:... |s1 |s2 |s3 |s4 |s5 | x | y | z | map | mindelay maxdelay homerange spawnrange spawnid maxcount | maxcount1 | maxcount2 | maxcount3 | maxcount4 | maxcount5 + // where s1-5 are additional spawn type entries with their own maxcounts + // the new format of each .map line is * |Dragon:Wyvern| spawns:spawns| | | | | 5209 | 965 | -40 | 2 | 2 | 10 | 50 | 30 | 1 + + linenumber++; + // is this the new format? + string[] args; + if (line.IndexOf('|') >= 0) + { + args = line.Trim().Split('|'); + newformat = true; + } + else + { + args = line.Trim().Split(' '); + } + + // determine the format of this line and parse accordingly + if (newformat) + { + ParseNewMapFormat(from, filename, line, args, linenumber, ref spawnercount, ref badspawnercount, ref overridemap, ref overridemintime, ref overridemaxtime); + } + else + { + ParseOldMapFormat(from, filename, line, args, linenumber, ref spawnercount, ref badspawnercount, ref overridemap, ref overridemintime, ref overridemaxtime); + } + + } + sr.Close(); + } + } + catch (Exception e) + { + // Let the user know what went wrong. + from.SendMessage("The file could not be read: {0}", e.Message); + } + from.SendMessage("Imported {0} spawners from {1}", spawnercount, filename); + from.SendMessage("{0} bad spawners detected", badspawnercount); + processedmaps = 1; + processedspawners = spawnercount; + } + else + // check to see if it is a directory + if (Directory.Exists(filename)) + { + // if so then import all of the .map files in the directory + string[] files = null; + try + { + files = Directory.GetFiles(filename, "*.map"); + } + catch { } + if (files != null && files.Length > 0) + { + from.SendMessage("Importing {0} .map files from directory {1}", files.Length, filename); + foreach (string file in files) + { + XmlImportMap(file, from, out processedmaps, out processedspawners); + total_processed_maps += processedmaps; + total_processed_spawners += processedspawners; + } + } + // recursively search subdirectories for more .map files + string[] dirs = null; + try + { + dirs = Directory.GetDirectories(filename); + } + catch { } + if (dirs != null && dirs.Length > 0) + { + foreach (string dir in dirs) + { + XmlImportMap(dir, from, out processedmaps, out processedspawners); + total_processed_maps += processedmaps; + total_processed_spawners += processedspawners; + } + } + from.SendMessage("Imported a total of {0} .map files and {2} spawners from directory {1}", total_processed_maps, filename, total_processed_spawners); + processedmaps = total_processed_maps; + processedspawners = total_processed_spawners; + } + else + { + from.SendMessage("{0} does not exist", filename); + } + } + + private static void ParseNewMapFormat(Mobile from, string filename, string line, string[] args, int linenumber, ref int spawnercount, ref int badspawnercount, ref int overridemap, ref double overridemintime, ref double overridemaxtime) + { + // format of each .map line is * Dragon:Wyvern 5209 965 -40 2 2 10 50 30 1 + // * typename:typename:... x y z map mindelay maxdelay homerange spawnrange maxcount + // or + // * typename:typename:... x y z map mindelay maxdelay homerange spawnrange spawnid maxcount + // ## are comments + // overridemap mapnumber + // map 0 is tram+fel + // map 1 is fel + // map 2 is tram + // map 3 is ilsh + // map 4 is mal + // map 5 is tokuno + // + // * | typename:typename:... | | | | | | x | y | z | map | mindelay maxdelay homerange spawnrange spawnid maxcount1 | maxcount2 | maxcount2 | maxcount3 | maxcount4 | maxcount5 | maxcount6 + // the new format of each .map line is * |Dragon:Wyvern| spawns:spawns| | | | | 5209 | 965 | -40 | 2 | 2 | 10 | 50 | 30 | 1 + + if (args == null || from == null) + { + return; + } + + // look for the override keyword + if (args.Length == 2 && args[0].ToLower() == "overridemap") + { + try + { + overridemap = int.Parse(args[1]); + } + catch { } + } + else + if (args.Length == 2 && args[0].ToLower() == "overridemintime") + { + try + { + overridemintime = double.Parse(args[1]); + } + catch { } + } + else + if (args.Length == 2 && args[0].ToLower() == "overridemaxtime") + { + try + { + overridemaxtime = double.Parse(args[1]); + } + catch { } + } + else + // look for a spawn spec line + if (args.Length > 0 && args[0] == "*") + { + + bool badspawn = false; + int x = 0; + int y = 0; + int z = 0; + int map = 0; + double mindelay = 0; + double maxdelay = 0; + int homerange = 0; + int spawnrange = 0; + string[][] typenames = new string[6][]; + + int[] maxcount = new int[6]; + + // parse the main args + + try + { + // get the list of spawns + for (int k = 0; k < 6; k++) + { + typenames[k] = args[k + 1].Split(':'); + } + + x = int.Parse(args[7]); + y = int.Parse(args[8]); + z = int.Parse(args[9]); + map = int.Parse(args[10]); + mindelay = double.Parse(args[11]); + maxdelay = double.Parse(args[12]); + homerange = int.Parse(args[13]); + spawnrange = int.Parse(args[14]); + int spawnid = int.Parse(args[15]); + + for (int k = 0; k < 6; k++) + { + maxcount[k] = int.Parse(args[k + 16]); + } + } + catch { from.SendMessage("Parsing error at line {0}", linenumber); badspawn = true; } + + // compute the total number of spawns + int totalspawns = 0; + int totalmaxcount = 0; + + for (int k = 0; k < 6; k++) + { + if (typenames[k] == null) + { + continue; + } + + for (int i = 0; i < typenames[k].Length; i++) + { + if (typenames[k][i] == null || typenames[k][i].Length == 0) + { + continue; + } + + totalspawns++; + } + + totalmaxcount += maxcount[k]; + } + + // apply min/maxdelay overrides + if (overridemintime != -1) + { + mindelay = overridemintime; + } + if (overridemaxtime != -1) + { + maxdelay = overridemaxtime; + } + if (mindelay > maxdelay) + { + maxdelay = mindelay; + } + + if (!badspawn && totalspawns > 0) + { + // everything seems ok so go ahead and make the spawner + // check for map override + if (overridemap >= 0) + { + map = overridemap; + } + + Map spawnmap = Map.Internal; + switch (map) + { + case 0: + { + spawnmap = Map.Felucca; + // note it also does trammel + break; + } + case 1: + { + spawnmap = Map.Felucca; + break; + } + case 2: + { + spawnmap = Map.Trammel; + break; + } + case 3: + { + spawnmap = Map.Ilshenar; + break; + } + case 4: + { + spawnmap = Map.Malas; + break; + } + case 5: + { + spawnmap = Map.Tokuno; + break; + } + } + + if (!IsValidMapLocation(x, y, spawnmap)) + { + // invalid so dont spawn it + badspawnercount++; + from.SendMessage("Invalid map/location at line {0}", linenumber); + from.SendMessage("Bad spawn at line {1}: {0}", line, linenumber); + return; + } + + // allow it to make an xmlspawner instead + // first add all of the creatures on the list + SpawnObject[] so = new SpawnObject[totalspawns]; + int count = 0; + bool hasvendor = true; + for (int k = 0; k < 6; k++) + { + if (typenames[k] == null) + { + continue; + } + + for (int i = 0; i < typenames[k].Length; i++) + { + if (typenames[k][i] == null || typenames[k][i].Length == 0 || count > totalspawns) + { + continue; + } + + so[count++] = new SpawnObject(typenames[k][i], maxcount[k]); + + // check the type to see if there are vendors on it + Type type = AssemblyHandler.FindTypeByName(typenames[k][i]); + + // check for vendor-only spawners which get special spawnrange treatment + if (type != null && type != typeof(BaseVendor) && !type.IsSubclassOf(typeof(BaseVendor))) + { + hasvendor = false; + } + + } + } + + // assign it a unique id + Guid SpawnId = Guid.NewGuid(); + + // and give it a name based on the spawner count and file + string spawnername = string.Format("{0}#{1}", Path.GetFileNameWithoutExtension(filename), spawnercount); + + // Create the new xml spawner + XmlSpawner spawner = new XmlSpawner(SpawnId, x, y, 0, 0, spawnername, totalmaxcount, + TimeSpan.FromMinutes(mindelay), TimeSpan.FromMinutes(maxdelay), TimeSpan.FromMinutes(0), -1, defaultTriggerSound, 1, + 0, homerange, false, so, TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), + TimeSpan.FromMinutes(0), null, null, null, null, null, + null, null, null, null, 1, null, false, defTODMode, defKillReset, false, -1, null, false, false, false, null, + TimeSpan.FromHours(0), null, false, null); + + spawner.SpawnRange = hasvendor ? 0 : spawnrange; + + spawner.m_PlayerCreated = true; + + spawner.MoveToWorld(new Point3D(x, y, z), spawnmap); + if (spawner.Map == Map.Internal) + { + badspawnercount++; + spawner.Delete(); + from.SendMessage("Invalid map at line {0}", linenumber); + from.SendMessage("Bad spawn at line {1}: {0}", line, linenumber); + return; + } + spawnercount++; + // handle the special case of map 0 that also needs to do trammel + if (map == 0) + { + spawnmap = Map.Trammel; + // assign it a unique id + SpawnId = Guid.NewGuid(); + // Create the new xml spawner + spawner = new XmlSpawner(SpawnId, x, y, 0, 0, spawnername, totalmaxcount, + TimeSpan.FromMinutes(mindelay), TimeSpan.FromMinutes(maxdelay), TimeSpan.FromMinutes(0), -1, defaultTriggerSound, 1, + 0, homerange, false, so, TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), + TimeSpan.FromMinutes(0), null, null, null, null, null, + null, null, null, null, 1, null, false, defTODMode, defKillReset, false, -1, null, false, false, false, null, + TimeSpan.FromHours(0), null, false, null) + { + SpawnRange = spawnrange, + m_PlayerCreated = true + }; + + spawner.MoveToWorld(new Point3D(x, y, z), spawnmap); + if (spawner.Map == Map.Internal) + { + badspawnercount++; + spawner.Delete(); + from.SendMessage("Bad spawn at line {1}: {0}", line, linenumber); + return; + } + spawnercount++; + } + } + else + { + badspawnercount++; + from.SendMessage("Bad spawn at line {1}: {0}", line, linenumber); + } + } + } + + private static void ParseOldMapFormat(Mobile from, string filename, string line, string[] args, int linenumber, ref int spawnercount, ref int badspawnercount, ref int overridemap, ref double overridemintime, ref double overridemaxtime) + { + // format of each .map line is * Dragon:Wyvern 5209 965 -40 2 2 10 50 30 1 + // * typename:typename:... x y z map mindelay maxdelay homerange spawnrange maxcount + // or + // * typename:typename:... x y z map mindelay maxdelay homerange spawnrange spawnid maxcount + // ## are comments + // overridemap mapnumber + // map 0 is tram+fel + // map 1 is fel + // map 2 is tram + // map 3 is ilsh + // map 4 is mal + // map 5 is tokuno + // + // * | typename:typename:... | | | | | | x | y | z | map | mindelay maxdelay homerange spawnrange spawnid maxcount | maxcount2 | maxcount2 | maxcount3 | maxcount4 | maxcount5 + // the new format of each .map line is * |Dragon:Wyvern| spawns:spawns| | | | | 5209 | 965 | -40 | 2 | 2 | 10 | 50 | 30 | 1 + + if (args == null || from == null) + { + return; + } + + // look for the override keyword + if (args.Length == 2 && args[0].ToLower() == "overridemap") + { + try + { + overridemap = int.Parse(args[1]); + } + catch { } + } + else + if (args.Length == 2 && args[0].ToLower() == "overridemintime") + { + try + { + overridemintime = double.Parse(args[1]); + } + catch { } + } + else + if (args.Length == 2 && args[0].ToLower() == "overridemaxtime") + { + try + { + overridemaxtime = double.Parse(args[1]); + } + catch { } + } + else + // look for a spawn spec line + if (args.Length > 0 && args[0] == "*") + { + bool badspawn = false; + int x = 0; + int y = 0; + int z = 0; + int map = 0; + double mindelay = 0; + double maxdelay = 0; + int homerange = 0; + int spawnrange = 0; + int maxcount = 0; + string[] typenames = null; + if (args.Length != 11 && args.Length != 12) + { + badspawn = true; + from.SendMessage("Invalid arg count {1} at line {0}", linenumber, args.Length); + } + else + { + // get the list of spawns + typenames = args[1].Split(':'); + // parse the rest of the args + + if (args.Length == 11) + { + + try + { + x = int.Parse(args[2]); + y = int.Parse(args[3]); + z = int.Parse(args[4]); + map = int.Parse(args[5]); + mindelay = double.Parse(args[6]); + maxdelay = double.Parse(args[7]); + homerange = int.Parse(args[8]); + spawnrange = int.Parse(args[9]); + maxcount = int.Parse(args[10]); + + } + catch { from.SendMessage("Parsing error at line {0}", linenumber); badspawn = true; } + } + else + if (args.Length == 12) + { + + try + { + x = int.Parse(args[2]); + y = int.Parse(args[3]); + z = int.Parse(args[4]); + map = int.Parse(args[5]); + mindelay = double.Parse(args[6]); + maxdelay = double.Parse(args[7]); + homerange = int.Parse(args[8]); + spawnrange = int.Parse(args[9]); + int spawnid = int.Parse(args[10]); + maxcount = int.Parse(args[11]); + + } + catch { from.SendMessage("Parsing error at line {0}", linenumber); badspawn = true; } + } + } + + + // apply mi/maxdelay overrides + if (overridemintime != -1) + { + mindelay = overridemintime; + } + if (overridemaxtime != -1) + { + maxdelay = overridemaxtime; + } + if (mindelay > maxdelay) + { + maxdelay = mindelay; + } + + if (!badspawn && typenames.Length > 0) + { + // everything seems ok so go ahead and make the spawner + // check for map override + if (overridemap >= 0) + { + map = overridemap; + } + + Map spawnmap = Map.Internal; + switch (map) + { + case 0: + { + spawnmap = Map.Felucca; + // note it also does trammel + break; + } + case 1: + { + spawnmap = Map.Felucca; + break; + } + case 2: + { + spawnmap = Map.Trammel; + break; + } + case 3: + { + spawnmap = Map.Ilshenar; + break; + } + case 4: + { + spawnmap = Map.Malas; + break; + } + case 5: + { + spawnmap = Map.Tokuno; + break; + } + } + + if (!IsValidMapLocation(x, y, spawnmap)) + { + // invalid so dont spawn it + badspawnercount++; + from.SendMessage("Invalid map/location at line {0}", linenumber); + from.SendMessage("Bad spawn at line {1}: {0}", line, linenumber); + return; + } + + // allow it to make an xmlspawner instead + // first add all of the creatures on the list + SpawnObject[] so = new SpawnObject[typenames.Length]; + + bool hasvendor = true; + for (int i = 0; i < typenames.Length; i++) + { + so[i] = new SpawnObject(typenames[i], maxcount); + + // check the type to see if there are vendors on it + Type type = AssemblyHandler.FindTypeByName(typenames[i]); + + // check for vendor-only spawners which get special spawnrange treatment + if (type != null && type != typeof(BaseVendor) && !type.IsSubclassOf(typeof(BaseVendor))) + { + hasvendor = false; + } + + } + + // assign it a unique id + Guid SpawnId = Guid.NewGuid(); + + // and give it a name based on the spawner count and file + string spawnername = string.Format("{0}#{1}", Path.GetFileNameWithoutExtension(filename), spawnercount); + + // Create the new xml spawner + XmlSpawner spawner = new XmlSpawner(SpawnId, x, y, 0, 0, spawnername, maxcount, + TimeSpan.FromMinutes(mindelay), TimeSpan.FromMinutes(maxdelay), TimeSpan.FromMinutes(0), -1, defaultTriggerSound, 1, + 0, homerange, false, so, TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), + TimeSpan.FromMinutes(0), null, null, null, null, null, + null, null, null, null, 1, null, false, defTODMode, defKillReset, false, -1, null, false, false, false, null, + TimeSpan.FromHours(0), null, false, null); + + spawner.SpawnRange = hasvendor ? 0 : spawnrange; + + spawner.m_PlayerCreated = true; + + spawner.MoveToWorld(new Point3D(x, y, z), spawnmap); + if (spawner.Map == Map.Internal) + { + badspawnercount++; + spawner.Delete(); + from.SendMessage("Invalid map at line {0}", linenumber); + from.SendMessage("Bad spawn at line {1}: {0}", line, linenumber); + return; + } + spawnercount++; + // handle the special case of map 0 that also needs to do trammel + if (map == 0) + { + spawnmap = Map.Trammel; + // assign it a unique id + SpawnId = Guid.NewGuid(); + // Create the new xml spawner + spawner = new XmlSpawner(SpawnId, x, y, 0, 0, spawnername, maxcount, + TimeSpan.FromMinutes(mindelay), TimeSpan.FromMinutes(maxdelay), TimeSpan.FromMinutes(0), -1, defaultTriggerSound, 1, + 0, homerange, false, so, TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), + TimeSpan.FromMinutes(0), null, null, null, null, null, + null, null, null, null, 1, null, false, defTODMode, defKillReset, false, -1, null, false, false, false, null, + TimeSpan.FromHours(0), null, false, null) + { + SpawnRange = spawnrange, + m_PlayerCreated = true + }; + + spawner.MoveToWorld(new Point3D(x, y, z), spawnmap); + if (spawner.Map == Map.Internal) + { + badspawnercount++; + spawner.Delete(); + from.SendMessage("Bad spawn at line {1}: {0}", line, linenumber); + return; + } + spawnercount++; + } + } + else + { + badspawnercount++; + from.SendMessage("Bad spawn at line {1}: {0}", line, linenumber); + } + } + } + + [Usage("XmlImportSpawners filename")] + [Description("Loads xml files created by Sno's xml exporter as xmlspawners.")] + public static void XmlImportSpawners_OnCommand(CommandEventArgs e) + { + if (e.Arguments.Length >= 1) + { + string filename = e.GetString(0); + string filePath = Path.Combine("Saves/Spawners", filename); + if (File.Exists(filePath)) + { + XmlDocument doc = new XmlDocument(); + try + { + doc.Load(filePath); + } + catch + { + e.Mobile.SendMessage("unable to load file {0}.", filePath); + return; + } + + XmlElement root = doc["spawners"]; + int successes = 0, failures = 0; + if (root?.GetElementsByTagName("spawner") != null) + { + foreach (XmlElement spawner in root.GetElementsByTagName("spawner")) + { + try + { + ImportSpawner(spawner, e.Mobile); + successes++; + } + catch (Exception ex) { e.Mobile.SendMessage(33, "{0} {1}", ex.Message, spawner.InnerText); failures++; } + } + } + e.Mobile.SendMessage("{0} spawners loaded successfully from {1}, {2} failures.", successes, filePath, failures); + } + else + { + e.Mobile.SendMessage("File {0} does not exist.", filePath); + } + } + else + { + e.Mobile.SendMessage("Usage: [XmlImportSpawners "); + } + } + + private static string GetText(XmlElement node, string defaultValue) + { + if (node == null) + { + return defaultValue; + } + + return node.InnerText; + } + + private static void ImportSpawner(XmlElement node, Mobile from) + { + int count = int.Parse(GetText(node["count"], "1")); + int homeRange = int.Parse(GetText(node["homerange"], "4")); + int walkingRange = int.Parse(GetText(node["walkingrange"], "-1")); + // width of the spawning area + int spawnwidth = homeRange * 2; + if (walkingRange >= 0) + { + spawnwidth = walkingRange * 2; + } + + int team = int.Parse(GetText(node["team"], "0")); + bool group = bool.Parse(GetText(node["group"], "False")); + TimeSpan maxDelay = TimeSpan.Parse(GetText(node["maxdelay"], "10:00")); + TimeSpan minDelay = TimeSpan.Parse(GetText(node["mindelay"], "05:00")); + List creaturesName = LoadCreaturesName(node["creaturesname"]); + string name = GetText(node["name"], "Spawner"); + Point3D location = Point3D.Parse(GetText(node["location"], "Error")); + Map map = Map.Parse(GetText(node["map"], "Error")); + + // allow it to make an xmlspawner instead + // first add all of the creatures on the list + SpawnObject[] so = new SpawnObject[creaturesName.Count]; + + bool hasvendor = false; + + for (int i = 0; i < creaturesName.Count; i++) + { + so[i] = new SpawnObject(creaturesName[i], count); + // check the type to see if there are vendors on it + Type type = AssemblyHandler.FindTypeByName(creaturesName[i]); + + // if it has basevendors on it or invalid types, then skip it + if (type != null && (type == typeof(BaseVendor) || type.IsSubclassOf(typeof(BaseVendor)))) + { + hasvendor = true; + } + } + + // assign it a unique id + Guid SpawnId = Guid.NewGuid(); + + // Create the new xml spawner + XmlSpawner spawner = new XmlSpawner(SpawnId, location.X, location.Y, spawnwidth, spawnwidth, name, count, + minDelay, maxDelay, TimeSpan.FromMinutes(0), -1, defaultTriggerSound, 1, + team, homeRange, false, so, TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), + TimeSpan.FromMinutes(0), null, null, null, null, null, + null, null, null, null, 1, null, group, defTODMode, defKillReset, false, -1, null, false, false, false, null, defDespawnTime, null, false, null); + + spawner.SpawnRange = hasvendor ? 0 : homeRange; + spawner.m_PlayerCreated = true; + + spawner.MoveToWorld(location, map); + if (!IsValidMapLocation(location, spawner.Map)) + { + spawner.Delete(); + throw new Exception("Invalid spawner location."); + } + } + + private static List LoadCreaturesName(XmlElement node) + { + List names = new List(); + + if (node != null) + { + foreach (XmlElement ele in node.GetElementsByTagName("creaturename")) + { + if (ele != null) + { + names.Add(ele.InnerText); + } + } + } + return names; + } + + [Usage("XmlImportMSF filename")] + [Description("Loads msf files created by Morxeton's megaspawner as xmlspawners.")] + public static void XmlImportMSF_OnCommand(CommandEventArgs e) + { + if (e.Arguments.Length >= 1) + { + /* + // I'm not sure what the default location for .msf files is + string filename = e.GetString(0); + string filePath = Path.Combine("Data/Megaspawner", filename); + */ + string filePath = e.GetString(0); + if (File.Exists(filePath)) + { + XmlDocument doc = new XmlDocument(); + doc.Load(filePath); + XmlElement root = doc["MegaSpawners"]; + if (root != null) + { + int successes = 0, failures = 0; + foreach (XmlElement spawner in root.GetElementsByTagName("MegaSpawner")) + { + + try + { + ImportMegaSpawner(e.Mobile, spawner); + successes++; + } + catch (Exception ex) { e.Mobile.SendMessage(33, "{0} {1}", ex.Message, spawner.InnerText); failures++; } + } + e.Mobile.SendMessage("{0} megaspawners loaded successfully from {1}, {2} failures.", successes, filePath, failures); + } + else + { + e.Mobile.SendMessage("Invalid .msf file. No MegaSpawners node found"); + } + } + else + { + e.Mobile.SendMessage("File {0} does not exist.", filePath); + } + } + else + { + e.Mobile.SendMessage("Usage: [XmlImportMSF "); + } + } + + private static void ImportMegaSpawner(Mobile from, XmlElement node) + { + string name = GetText(node["Name"], "MegaSpawner"); + bool running = bool.Parse(GetText(node["Active"], "True")); + Point3D location = Point3D.Parse(GetText(node["Location"], "Error")); + Map map = Map.Parse(GetText(node["Map"], "Error")); + + + int team = 0; + bool group = false; + int maxcount = 0; // default maxcount of the spawner + int homeRange = 4; // default homerange + int spawnRange = 4; // default homerange + TimeSpan maxDelay = TimeSpan.FromMinutes(10); + TimeSpan minDelay = TimeSpan.FromMinutes(5); + + XmlElement listnode = node["EntryLists"]; + + int nentries = 0; + SpawnObject[] so = null; + + + if (listnode != null) + { + // get the number of entries + if (listnode.HasAttributes) + { + XmlAttributeCollection attr = listnode.Attributes; + + nentries = int.Parse(attr.GetNamedItem("count").Value); + } + if (nentries > 0) + { + so = new SpawnObject[nentries]; + + int entrycount = 0; + bool diff = false; + foreach (XmlElement entrynode in listnode.GetElementsByTagName("EntryList")) + { + // go through each entry and add a spawn object for it + if (entrynode != null) + { + if (entrycount == 0) + { + // get the spawner defaults from the first entry + // dont handle the individually specified entry attributes + group = bool.Parse(GetText(entrynode["GroupSpawn"], "False")); + maxDelay = TimeSpan.FromSeconds(int.Parse(GetText(entrynode["MaxDelay"], "10:00"))); + minDelay = TimeSpan.FromSeconds(int.Parse(GetText(entrynode["MinDelay"], "05:00"))); + homeRange = int.Parse(GetText(entrynode["WalkRange"], "10")); + spawnRange = int.Parse(GetText(entrynode["SpawnRange"], "4")); + } + else + { + // just check for consistency with other entries and report discrepancies + if (group != bool.Parse(GetText(entrynode["GroupSpawn"], "False"))) + { + diff = true; + // log it + try + { + using (StreamWriter op = new StreamWriter("badimport.log", true)) + { + op.WriteLine("MSFimport : individual group entry difference: {0} vs {1}", + GetText(entrynode["GroupSpawn"], "False"), group); + + } + } + catch { } + } + if (minDelay != TimeSpan.FromSeconds(int.Parse(GetText(entrynode["MinDelay"], "05:00")))) + { + diff = true; + // log it + try + { + using (StreamWriter op = new StreamWriter("badimport.log", true)) + { + op.WriteLine("MSFimport : individual mindelay entry difference: {0} vs {1}", + GetText(entrynode["MinDelay"], "05:00"), minDelay); + + } + } + catch { } + } + if (maxDelay != TimeSpan.FromSeconds(int.Parse(GetText(entrynode["MaxDelay"], "10:00")))) + { + diff = true; + // log it + try + { + using (StreamWriter op = new StreamWriter("badimport.log", true)) + { + op.WriteLine("MSFimport : individual maxdelay entry difference: {0} vs {1}", + GetText(entrynode["MaxDelay"], "10:00"), maxDelay); + + } + } + catch { } + } + if (homeRange != int.Parse(GetText(entrynode["WalkRange"], "10"))) + { + diff = true; + // log it + try + { + using (StreamWriter op = new StreamWriter("badimport.log", true)) + { + op.WriteLine("MSFimport : individual homerange entry difference: {0} vs {1}", + GetText(entrynode["WalkRange"], "10"), homeRange); + + } + } + catch { } + } + if (spawnRange != int.Parse(GetText(entrynode["SpawnRange"], "4"))) + { + diff = true; + // log it + try + { + using (StreamWriter op = new StreamWriter("badimport.log", true)) + { + op.WriteLine("MSFimport : individual spawnrange entry difference: {0} vs {1}", + GetText(entrynode["SpawnRange"], "4"), spawnRange); + + } + } + catch { } + } + } + + // these apply to individual entries + int amount = int.Parse(GetText(entrynode["Amount"], "1")); + string entryname = GetText(entrynode["EntryType"], ""); + + // keep track of the maxcount for the spawner by adding the individual amounts + maxcount += amount; + + // add the creature entry + so[entrycount] = new SpawnObject(entryname, amount); + + entrycount++; + if (entrycount > nentries) + { + // log it + try + { + using (StreamWriter op = new StreamWriter("badimport.log", true)) + { + op.WriteLine("{0} MSFImport Error; inconsistent entry count {1} {2}", DateTime.UtcNow, location, map); + op.WriteLine(); + } + } + catch { } + from.SendMessage("Inconsistent entry count detected at {0} {1}.", location, map); + break; + } + + } + } + if (diff) + { + from.SendMessage("Individual entry setting detected at {0} {1}.", location, map); + // log it + try + { + using (StreamWriter op = new StreamWriter("badimport.log", true)) + { + op.WriteLine("{0} MSFImport: Individual entry setting differences listed above from spawner at {1} {2}", DateTime.UtcNow, location, map); + op.WriteLine(); + } + } + catch { } + } + } + } + + // assign it a unique id + Guid SpawnId = Guid.NewGuid(); + // Create the new xml spawner + XmlSpawner spawner = new XmlSpawner(SpawnId, location.X, location.Y, 0, 0, name, maxcount, + minDelay, maxDelay, TimeSpan.FromMinutes(0), -1, defaultTriggerSound, 1, + team, homeRange, false, so, TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), + TimeSpan.FromMinutes(0), null, null, null, null, null, + null, null, null, null, 1, null, group, defTODMode, defKillReset, false, -1, null, false, false, false, null, defDespawnTime, null, false, null) + { + SpawnRange = spawnRange, + m_PlayerCreated = true + }; + + // Try to find a valid Z height if required (Z == -999) + + if (location.Z == -999) + { + int NewZ = map.GetAverageZ(location.X, location.Y); + + if (map.CanFit(location.X, location.Y, NewZ, SpawnFitSize) == false) + { + for (int x = 1; x <= 39; x++) + { + if (map.CanFit(location.X, location.Y, NewZ + x, SpawnFitSize)) + { + NewZ += x; + break; + } + } + } + location.Z = NewZ; + } + + spawner.MoveToWorld(location, map); + + if (!IsValidMapLocation(location, spawner.Map)) + { + spawner.Delete(); + throw new Exception("Invalid spawner location."); + } + } + + + + public static void XmlLoadFromFile(string filename, string SpawnerPrefix, Mobile from, Point3D fromloc, Map frommap, bool loadrelative, int maxrange, bool loadnew, out int processedmaps, out int processedspawners) + { + processedmaps = 0; + processedspawners = 0; + int total_processed_maps = 0; + int total_processed_spawners = 0; + + if (filename == null || filename.Length <= 0) + { + return; + } + + // Check if the file exists + if (File.Exists(filename)) + { + FileStream fs = null; + try + { + fs = File.Open(filename, FileMode.Open, FileAccess.Read); + } + catch { } + + if (fs == null) + { + if (from != null) + { + from.SendMessage("Unable to open {0} for loading", filename); + } + + return; + } + + // load the file + XmlLoadFromStream(fs, filename, SpawnerPrefix, from, fromloc, frommap, loadrelative, maxrange, loadnew, out processedmaps, out processedspawners); + + } + else if (Directory.Exists(filename)) + { + // if so then load all of the .xml files in the directory + string[] files = null; + try + { + files = Directory.GetFiles(filename, "*.xml"); + } + catch { } + if (files != null && files.Length > 0) + { + if (from != null) + { + from.SendMessage("Loading {0} .xml files from directory {1}", files.Length, filename); + } + + foreach (string file in files) + { + XmlLoadFromFile(file, SpawnerPrefix, from, fromloc, frommap, loadrelative, maxrange, loadnew, out processedmaps, out processedspawners); + total_processed_maps += processedmaps; + total_processed_spawners += processedspawners; + } + } + // recursively search subdirectories for more .xml files + string[] dirs = null; + try + { + dirs = Directory.GetDirectories(filename); + } + catch { } + if (dirs != null && dirs.Length > 0) + { + foreach (string dir in dirs) + { + XmlLoadFromFile(dir, SpawnerPrefix, from, fromloc, frommap, loadrelative, maxrange, loadnew, out processedmaps, out processedspawners); + total_processed_maps += processedmaps; + total_processed_spawners += processedspawners; + } + } + if (from != null) + { + from.SendMessage("Loaded a total of {0} .xml files and {2} spawners from directory {1}", total_processed_maps, filename, total_processed_spawners); + } + + processedmaps = total_processed_maps; + processedspawners = total_processed_spawners; + } + else + { + if (from != null) + { + from.SendMessage("{0} does not exist", filename); + } + } + + } + + public static void XmlLoadFromFile(string filename, string SpawnerPrefix, Mobile from, bool loadrelative, int maxrange, bool loadnew, out int processedmaps, out int processedspawners) + { + processedmaps = 0; + processedspawners = 0; + + if (from == null) + { + return; + } + + XmlLoadFromFile(filename, SpawnerPrefix, from, from.Location, from.Map, loadrelative, maxrange, loadnew, out processedmaps, out processedspawners); + } + + public static void XmlLoadFromFile(string filename, string SpawnerPrefix, Point3D fromloc, Map frommap, bool loadrelative, int maxrange, bool loadnew, out int processedmaps, out int processedspawners) + { + XmlLoadFromFile(filename, SpawnerPrefix, null, fromloc, frommap, loadrelative, maxrange, loadnew, out processedmaps, out processedspawners); + + } + + public static void XmlLoadFromFile(string filename, string SpawnerPrefix, bool loadnew, out int processedmaps, out int processedspawners) + { + XmlLoadFromFile(filename, SpawnerPrefix, null, Point3D.Zero, Map.Internal, false, 0, loadnew, out processedmaps, out processedspawners); + + } + + + public static void XmlLoadFromStream(Stream fs, string filename, string SpawnerPrefix, Mobile from, Point3D fromloc, Map frommap, bool loadrelative, int maxrange, bool loadnew, out int processedmaps, out int processedspawners) + { + XmlLoadFromStream(fs, filename, SpawnerPrefix, from, fromloc, frommap, loadrelative, maxrange, loadnew, out processedmaps, out processedspawners, false); + } + + public static void XmlLoadFromStream(Stream fs, string filename, string SpawnerPrefix, Mobile from, Point3D fromloc, Map frommap, bool loadrelative, int maxrange, bool loadnew, out int processedmaps, out int processedspawners, bool verbose) + { + processedmaps = 0; + processedspawners = 0; + + if (fs == null) + { + return; + } + + // assign an id that will be used to distinguish the newly loaded spawners by appending it to their name + Guid newloadid = Guid.NewGuid(); + + int TotalCount = 0; + int TrammelCount = 0; + int FeluccaCount = 0; + int IlshenarCount = 0; + int MalasCount = 0; + int TokunoCount = 0; + int OtherCount = 0; + bool questionable_spawner = false; + bool bad_spawner = false; + int badcount = 0; + int questionablecount = 0; + + int failedobjectitemcount = 0; + int failedsetitemcount = 0; + int relativex = -1; + int relativey = -1; + int relativez = 0; + Map relativemap = null; + + if (from != null) + { + from.SendMessage($"Loading {"XmlSpawner"} objects{(!string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" : string.Empty)} from file {filename}."); + } + + // Create the data set + DataSet ds = new DataSet(SpawnDataSetName); + + // Read in the file + bool fileerror = false; + try + { + ds.ReadXml(fs); + } + catch + { + if (from != null) + { + from.SendMessage(33, "Error reading xml file {0}", filename); + } + + fileerror = true; + } + // close the file + fs.Close(); + if (fileerror) + { + return; + } + + // Check that at least a single table was loaded + if (ds.Tables.Count > 0) + { + // Add each spawn point to the current map + if (ds.Tables[SpawnTablePointName] != null && ds.Tables[SpawnTablePointName].Rows.Count > 0) + { + foreach (DataRow dr in ds.Tables[SpawnTablePointName].Rows) + { + // load in the spawner info. Certain fields are required and therefore cannot be ignored + // the exception handler for those will flag bad_spawner and the result will be logged + + // Each row makes up a single spawner + string SpawnName = "Spawner"; + try { SpawnName = (string)dr["Name"]; } + catch { questionable_spawner = true; } + + if (loadnew) + { + // append the new id to the name + SpawnName = string.Format("{0}-{1}", SpawnName, newloadid); + } + + // Check if there is any spawner name criteria specified on the load + if (string.IsNullOrEmpty(SpawnerPrefix) || SpawnName.StartsWith(SpawnerPrefix)) + { + // Try load the GUID (might not work so create a new GUID) + Guid SpawnId = Guid.NewGuid(); + if (!loadnew) + { + try { SpawnId = new Guid((string)dr["UniqueId"]); } + catch { } + } + else + { + // change the dataset guid to the newly created one when new loading + try + { + dr["UniqueId"] = SpawnId; + } + catch { Console.WriteLine("unable to set UniqueId"); } + } + + int SpawnCentreX = fromloc.X; + int SpawnCentreY = fromloc.Y; + int SpawnCentreZ = fromloc.Z; + + try { SpawnCentreX = int.Parse((string)dr["CentreX"]); } + catch { bad_spawner = true; } + try { SpawnCentreY = int.Parse((string)dr["CentreY"]); } + catch { bad_spawner = true; } + try { SpawnCentreZ = int.Parse((string)dr["CentreZ"]); } + catch { bad_spawner = true; } + + int SpawnX = SpawnCentreX; + int SpawnY = SpawnCentreY; + int SpawnWidth = 0; + int SpawnHeight = 0; + try { SpawnX = int.Parse((string)dr["X"]); } + catch { questionable_spawner = true; } + try { SpawnY = int.Parse((string)dr["Y"]); } + catch { questionable_spawner = true; } + try { SpawnWidth = int.Parse((string)dr["Width"]); } + catch { questionable_spawner = true; } + try { SpawnHeight = int.Parse((string)dr["Height"]); } + catch { questionable_spawner = true; } + + // Try load the InContainer (default to false) + bool InContainer = false; + int ContainerX = 0; + int ContainerY = 0; + int ContainerZ = 0; + try { InContainer = bool.Parse((string)dr["InContainer"]); } + catch { } + if (InContainer) + { + try { ContainerX = int.Parse((string)dr["ContainerX"]); } + catch { } + try { ContainerY = int.Parse((string)dr["ContainerY"]); } + catch { } + try { ContainerZ = int.Parse((string)dr["ContainerZ"]); } + catch { } + } + + // Get the map (default to the mobiles map) if the relative distance is too great, then use the defined map + + Map SpawnMap = frommap; + + string XmlMapName = frommap.Name; + + //if (!loadrelative && !loadnew) + { + // Try to get the "map" field, but in case it doesn't exist, catch and discard the exception + try { XmlMapName = (string)dr["Map"]; } + catch { questionable_spawner = true; } + + // Convert the xml map value to a real map object + if (string.Compare(XmlMapName, Map.Trammel.Name, true) == 0 || XmlMapName == "Trammel") + { + SpawnMap = Map.Trammel; + TrammelCount++; + } + else if (string.Compare(XmlMapName, Map.Felucca.Name, true) == 0 || XmlMapName == "Felucca") + { + SpawnMap = Map.Felucca; + FeluccaCount++; + } + else if (string.Compare(XmlMapName, Map.Ilshenar.Name, true) == 0 || XmlMapName == "Ilshenar") + { + SpawnMap = Map.Ilshenar; + IlshenarCount++; + } + else if (string.Compare(XmlMapName, Map.Malas.Name, true) == 0 || XmlMapName == "Malas") + { + SpawnMap = Map.Malas; + MalasCount++; + } + else if (string.Compare(XmlMapName, Map.Tokuno.Name, true) == 0 || XmlMapName == "Tokuno") + { + SpawnMap = Map.Tokuno; + TokunoCount++; + } + else + { + try + { + SpawnMap = Map.Parse(XmlMapName); + } + catch { } + OtherCount++; + } + } + + // test to see whether the distance between the relative center point and the spawner is too great. If so then dont do relative + if (relativex == -1 && relativey == -1) + { + // the first xml entry in the file will determine the origin + relativex = SpawnCentreX; + relativey = SpawnCentreY; + relativez = SpawnCentreZ; + + // and also the relative map to relocate from + relativemap = SpawnMap; + } + + int SpawnRelZ = 0; + int OrigZ = SpawnCentreZ; + if (loadrelative && Math.Abs(relativex - SpawnCentreX) <= maxrange && Math.Abs(relativey - SpawnCentreY) <= maxrange + && SpawnMap == relativemap) + { + // its within range so shift it + SpawnCentreX -= relativex - fromloc.X; + SpawnCentreY -= relativey - fromloc.Y; + SpawnX -= relativex - fromloc.X; + SpawnY -= relativey - fromloc.Y; + // force it to autosearch for Z when it places it but hold onto relative Z info just in case it can be placed there + SpawnRelZ = relativez - fromloc.Z; + SpawnCentreZ = short.MinValue; + } + + // if relative loading has been specified, see if the loaded map is the same as the relativemap and relocate. + // if it doesnt match then just leave it + if (loadrelative && relativemap == SpawnMap) + { + SpawnMap = frommap; + } + + + if (SpawnMap == Map.Internal) + { + bad_spawner = true; + } + + // Try load the IsRelativeHomeRange (default to true) + bool SpawnIsRelativeHomeRange = true; + try { SpawnIsRelativeHomeRange = bool.Parse((string)dr["IsHomeRangeRelative"]); } + catch { } + + + int SpawnHomeRange = 5; + try { SpawnHomeRange = int.Parse((string)dr["Range"]); } + catch { questionable_spawner = true; } + int SpawnMaxCount = 1; + try { SpawnMaxCount = int.Parse((string)dr["MaxCount"]); } + catch { questionable_spawner = true; } + + //deal with double format for delay. default is the old minute format + bool delay_in_sec = false; + try { delay_in_sec = bool.Parse((string)dr["DelayInSec"]); } + catch { } + TimeSpan SpawnMinDelay = TimeSpan.FromMinutes(5); + TimeSpan SpawnMaxDelay = TimeSpan.FromMinutes(10); + + + if (delay_in_sec) + { + try { SpawnMinDelay = TimeSpan.FromSeconds(int.Parse((string)dr["MinDelay"])); } + catch { } + try { SpawnMaxDelay = TimeSpan.FromSeconds(int.Parse((string)dr["MaxDelay"])); } + catch { } + } + else + { + try { SpawnMinDelay = TimeSpan.FromMinutes(int.Parse((string)dr["MinDelay"])); } + catch { } + try { SpawnMaxDelay = TimeSpan.FromMinutes(int.Parse((string)dr["MaxDelay"])); } + catch { } + } + TimeSpan SpawnMinRefractory = TimeSpan.FromMinutes(0); + try { SpawnMinRefractory = TimeSpan.FromMinutes(double.Parse((string)dr["MinRefractory"])); } + catch { } + + TimeSpan SpawnMaxRefractory = TimeSpan.FromMinutes(0); + try { SpawnMaxRefractory = TimeSpan.FromMinutes(double.Parse((string)dr["MaxRefractory"])); } + catch { } + + TimeSpan SpawnTODStart = TimeSpan.FromMinutes(0); + try { SpawnTODStart = TimeSpan.FromMinutes(double.Parse((string)dr["TODStart"])); } + catch { } + + TimeSpan SpawnTODEnd = TimeSpan.FromMinutes(0); + try { SpawnTODEnd = TimeSpan.FromMinutes(double.Parse((string)dr["TODEnd"])); } + catch { } + + int todmode = (int)TODModeType.Realtime; + TODModeType SpawnTODMode = TODModeType.Realtime; + try { todmode = int.Parse((string)dr["TODMode"]); } + catch { } + switch (todmode) + { + case (int)TODModeType.Gametime: + { + SpawnTODMode = TODModeType.Gametime; + break; + } + case (int)TODModeType.Realtime: + { + SpawnTODMode = TODModeType.Realtime; + break; + } + } + + int SpawnKillReset = defKillReset; + try { SpawnKillReset = int.Parse((string)dr["KillReset"]); } + catch { } + + string SpawnProximityMessage = null; + // proximity message + try { SpawnProximityMessage = (string)dr["ProximityTriggerMessage"]; } + catch { } + + string SpawnItemTriggerName = null; + try { SpawnItemTriggerName = (string)dr["ItemTriggerName"]; } + catch { } + + string SpawnNoItemTriggerName = null; + try { SpawnNoItemTriggerName = (string)dr["NoItemTriggerName"]; } + catch { } + + string SpawnSpeechTrigger = null; + try { SpawnSpeechTrigger = (string)dr["SpeechTrigger"]; } + catch { } + + string SpawnSkillTrigger = null; + try { SpawnSkillTrigger = (string)dr["SkillTrigger"]; } + catch { } + + string SpawnMobTriggerName = null; + try { SpawnMobTriggerName = (string)dr["MobTriggerName"]; } + catch { } + string SpawnMobPropertyName = null; + try { SpawnMobPropertyName = (string)dr["MobPropertyName"]; } + catch { } + string SpawnPlayerPropertyName = null; + try { SpawnPlayerPropertyName = (string)dr["PlayerPropertyName"]; } + catch { } + + double SpawnTriggerProbability = 1; + try { SpawnTriggerProbability = double.Parse((string)dr["TriggerProbability"]); } + catch { } + + int SpawnSequentialSpawning = -1; + try { SpawnSequentialSpawning = int.Parse((string)dr["SequentialSpawning"]); } + catch { } + + string SpawnRegionName = null; + try { SpawnRegionName = (string)dr["RegionName"]; } + catch { } + + string SpawnConfigFile = null; + try { SpawnConfigFile = (string)dr["ConfigFile"]; } + catch { } + + bool SpawnAllowGhost = false; + try { SpawnAllowGhost = bool.Parse((string)dr["AllowGhostTriggering"]); } + catch { } + + bool SpawnAllowNPC = false; + try { SpawnAllowNPC = bool.Parse((string)dr["AllowNPCTriggering"]); } + catch { } + + bool SpawnSpawnOnTrigger = false; + try { SpawnSpawnOnTrigger = bool.Parse((string)dr["SpawnOnTrigger"]); } + catch { } + + bool SpawnSmartSpawning = false; + try { SpawnSmartSpawning = bool.Parse((string)dr["SmartSpawning"]); } + catch { } + + bool TickReset = false; + try { TickReset = bool.Parse((string)dr["TickReset"]); } + catch { } + + string SpawnObjectPropertyName = null; + try { SpawnObjectPropertyName = (string)dr["ObjectPropertyName"]; } + catch { } + + // we will assign this during the self-reference resolution pass + Item SpawnSetPropertyItem = null; + + // we will assign this during the self-reference resolution pass + Item SpawnObjectPropertyItem = null; + + // read the duration parameter from the xml file + // but older files wont have it so deal with that condition and set it to the default of "0", i.e. infinite duration + // Try to get the "Duration" field, but in case it doesn't exist, catch and discard the exception + TimeSpan SpawnDuration = TimeSpan.FromMinutes(0); + try { SpawnDuration = TimeSpan.FromMinutes(double.Parse((string)dr["Duration"])); } + catch { } + + TimeSpan SpawnDespawnTime = TimeSpan.FromHours(0); + try { SpawnDespawnTime = TimeSpan.FromHours(double.Parse((string)dr["DespawnTime"])); } + catch { } + int SpawnProximityRange = -1; + // Try to get the "ProximityRange" field, but in case it doesn't exist, catch and discard the exception + try { SpawnProximityRange = int.Parse((string)dr["ProximityRange"]); } + catch { } + + int SpawnProximityTriggerSound = 0; + // Try to get the "ProximityTriggerSound" field, but in case it doesn't exist, catch and discard the exception + try { SpawnProximityTriggerSound = int.Parse((string)dr["ProximityTriggerSound"]); } + catch { } + + int SpawnAmount = 1; + try { SpawnAmount = int.Parse((string)dr["Amount"]); } + catch { } + + bool SpawnExternalTriggering = false; + try { SpawnExternalTriggering = bool.Parse((string)dr["ExternalTriggering"]); } + catch { } + + string waypointstr = null; + try { waypointstr = (string)dr["Waypoint"]; } + catch { } + + WayPoint SpawnWaypoint = GetWaypoint(waypointstr); + + int SpawnTeam = 0; + try { SpawnTeam = int.Parse((string)dr["Team"]); } + catch { questionable_spawner = true; } + bool SpawnIsGroup = false; + try { SpawnIsGroup = bool.Parse((string)dr["IsGroup"]); } + catch { questionable_spawner = true; } + bool SpawnIsRunning = false; + try { SpawnIsRunning = bool.Parse((string)dr["IsRunning"]); } + catch { questionable_spawner = true; } + // try loading the new spawn specifications first + SpawnObject[] Spawns = new SpawnObject[0]; + bool havenew = true; + try { Spawns = SpawnObject.LoadSpawnObjectsFromString2((string)dr["Objects2"]); } + catch { havenew = false; } + if (!havenew) + { + // try loading the new spawn specifications + try { Spawns = SpawnObject.LoadSpawnObjectsFromString((string)dr["Objects"]); } + catch { questionable_spawner = true; } + // can only have one of these defined + } + + // do a check on the location of the spawner + if (!IsValidMapLocation(SpawnCentreX, SpawnCentreY, SpawnMap)) + { + if (from != null) + { + from.SendMessage(33, "Invalid location '{0}' at [{1} {2}] in {3}", + SpawnName, SpawnCentreX, SpawnCentreY, XmlMapName); + } + + bad_spawner = true; + } + + // Check if this spawner already exists + XmlSpawner OldSpawner = null; + bool found_container = false; + bool found_spawner = false; + Container spawn_container = null; + if (!bad_spawner) + { + foreach (Item i in World.Items.Values) + { + if (i is XmlSpawner checkXmlSpawner) + { + // Check if the spawners GUID is the same as the one being loaded + // and that the spawners map is the same as the one being loaded + if (checkXmlSpawner.UniqueId == SpawnId.ToString() + /* && (CheckXmlSpawner.Map == SpawnMap || loadrelative)*/) + { + OldSpawner = checkXmlSpawner; + found_spawner = true; + } + } + + //look for containers with the spawn coordinates if the incontainer flag is set + if (InContainer && !found_container && i is Container container && SpawnCentreX == container.Location.X && SpawnCentreY == container.Location.Y && + (SpawnCentreZ == container.Location.Z || SpawnCentreZ == short.MinValue)) + { + // assume this is the container that the spawner was in + found_container = true; + spawn_container = container; + } + // ok we can break if we have handled both the spawner and any containers + if (found_spawner && (found_container || !InContainer)) + { + break; + } + } + } + + // test to see whether the spawner specification was valid, bad, or questionable + if (bad_spawner) + { + badcount++; + if (from != null) + { + from.SendMessage(33, "Invalid spawner"); + } + + // log it + long fileposition = -1; + try { fileposition = fs.Position; } + catch { } + try + { + using (StreamWriter op = new StreamWriter("badxml.log", true)) + { + op.WriteLine("# Invalid spawner : {0}: Fileposition {1} {2}", DateTime.UtcNow, fileposition, filename); + op.WriteLine(); + } + } + catch { } + } + else + if (questionable_spawner) + { + questionablecount++; + if (from != null) + { + from.SendMessage(33, "Questionable spawner '{0}' at [{1} {2}] in {3}", + SpawnName, SpawnCentreX, SpawnCentreY, XmlMapName); + } + + // log it + long fileposition = -1; + try { fileposition = fs.Position; } + catch { } + try + { + using (StreamWriter op = new StreamWriter("badxml.log", true)) + { + op.WriteLine("# Questionable spawner : {0}: Format: X Y Z Map SpawnerName Fileposition Xmlfile", DateTime.UtcNow); + op.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}", SpawnCentreX, SpawnCentreY, SpawnCentreZ, XmlMapName, SpawnName, fileposition, filename); + op.WriteLine(); + } + } + catch { } + } + if (!bad_spawner) + { + // Delete the old spawner if it exists + if (OldSpawner != null) + { + OldSpawner.Delete(); + } + + // Create the new spawner + XmlSpawner TheSpawn = new XmlSpawner(SpawnId, SpawnX, SpawnY, SpawnWidth, SpawnHeight, SpawnName, SpawnMaxCount, + SpawnMinDelay, SpawnMaxDelay, SpawnDuration, SpawnProximityRange, SpawnProximityTriggerSound, SpawnAmount, + SpawnTeam, SpawnHomeRange, SpawnIsRelativeHomeRange, Spawns, SpawnMinRefractory, SpawnMaxRefractory, SpawnTODStart, + SpawnTODEnd, SpawnObjectPropertyItem, SpawnObjectPropertyName, SpawnProximityMessage, SpawnItemTriggerName, SpawnNoItemTriggerName, + SpawnSpeechTrigger, SpawnMobTriggerName, SpawnMobPropertyName, SpawnPlayerPropertyName, SpawnTriggerProbability, + SpawnSetPropertyItem, SpawnIsGroup, SpawnTODMode, SpawnKillReset, SpawnExternalTriggering, SpawnSequentialSpawning, + SpawnRegionName, SpawnAllowGhost, SpawnAllowNPC, SpawnSpawnOnTrigger, SpawnConfigFile, SpawnDespawnTime, SpawnSkillTrigger, SpawnSmartSpawning, SpawnWaypoint) + { + m_DisableGlobalAutoReset = TickReset + }; + + // Try to find a valid Z height if required (SpawnCentreZ = short.MinValue) + int NewZ = 0; + + // Check if relative loading is set. If so then try loading at the z-offset position first with no surface requirement, then try auto + /*if (loadrelative && SpawnMap.CanFit(SpawnCentreX, SpawnCentreY, OrigZ - SpawnRelZ, SpawnFitSize,true, false,false)) */ + + if (loadrelative && HasTileSurface(SpawnMap, SpawnCentreX, SpawnCentreY, OrigZ - SpawnRelZ)) + { + NewZ = OrigZ - SpawnRelZ; + } + else if (SpawnCentreZ == short.MinValue) + { + NewZ = SpawnMap.GetAverageZ(SpawnCentreX, SpawnCentreY); + + if (SpawnMap.CanFit(SpawnCentreX, SpawnCentreY, NewZ, SpawnFitSize) == false) + { + for (int x = 1; x <= 39; x++) + { + if (SpawnMap.CanFit(SpawnCentreX, SpawnCentreY, NewZ + x, SpawnFitSize)) + { + NewZ += x; + break; + } + } + } + } + else + { + // This spawn point already has a defined Z location, so use it + NewZ = SpawnCentreZ; + } + + // if this is a container held spawner, drop it in the container + if (found_container && spawn_container != null && !spawn_container.Deleted) + { + TheSpawn.Location = new Point3D(ContainerX, ContainerY, ContainerZ); + spawn_container.AddItem(TheSpawn); + } + else + { + // disable the X_Y adjustments in OnLocationChange + IgnoreLocationChange = true; + TheSpawn.MoveToWorld(new Point3D(SpawnCentreX, SpawnCentreY, NewZ), SpawnMap); + } + + // reset the spawner + TheSpawn.Reset(); + TheSpawn.Running = SpawnIsRunning; + + // update subgroup-specific next spawn times + TheSpawn.NextSpawn = TimeSpan.Zero; + TheSpawn.ResetNextSpawnTimes(); + + + // Send a message to the client that the spawner is created + if (from != null && verbose) + { + from.SendMessage(188, "Created '{0}' in {1} at {2}", TheSpawn.Name, TheSpawn.Map.Name, TheSpawn.Location.ToString()); + } + + // Do a total respawn + //TheSpawn.Respawn(); + + // Increment the count + TotalCount++; + } + bad_spawner = false; + questionable_spawner = false; + } + } + } + + if (from != null) + { + from.SendMessage("Resolving spawner self references"); + } + + if (ds.Tables[SpawnTablePointName] != null && ds.Tables[SpawnTablePointName].Rows.Count > 0) + { + foreach (DataRow dr in ds.Tables[SpawnTablePointName].Rows) + { + // Try load the GUID + bool badid = false; + Guid SpawnId = Guid.NewGuid(); + try { SpawnId = new Guid((string)dr["UniqueId"]); } + catch { badid = true; } + if (badid) + { + continue; + } + + // Get the map + Map SpawnMap = frommap; + string XmlMapName = frommap.Name; + + if (!loadrelative) + { + try { XmlMapName = (string)dr["Map"]; } + catch { } + + // Convert the xml map value to a real map object + try + { + SpawnMap = Map.Parse(XmlMapName); + } + catch { } + } + + bool found_spawner = false; + XmlSpawner OldSpawner = null; + foreach (Item i in World.Items.Values) + { + if (i is XmlSpawner checkXmlSpawner) + { + // Check if the spawners GUID is the same as the one being loaded + // and that the spawners map is the same as the one being loaded + if (checkXmlSpawner.UniqueId == SpawnId.ToString() + /* && (CheckXmlSpawner.Map == SpawnMap || loadrelative) */) + { + OldSpawner = checkXmlSpawner; + found_spawner = true; + } + } + + if (found_spawner) + { + break; + } + } + + if (found_spawner && OldSpawner != null && !OldSpawner.Deleted) + { + // resolve item name references since they may have referred to spawners that were just created + string setObjectName = null; + try { setObjectName = (string)dr["SetPropertyItemName"]; } + catch { } + if (!string.IsNullOrEmpty(setObjectName)) + { + // try to parse out the type information if it has also been saved + string[] typeargs = setObjectName.Split(",".ToCharArray(), 2); + string typestr = null; + string namestr = setObjectName; + + if (typeargs.Length > 1) + { + namestr = typeargs[0]; + typestr = typeargs[1]; + } + + // if this is a new load then assume that it will be referring to another newly loaded object so append the newloadid + if (loadnew) + { + string tmpsetObjectName = string.Format("{0}-{1}", namestr, newloadid); + OldSpawner.m_SetPropertyItem = BaseXmlSpawner.FindItemByName(null, tmpsetObjectName, typestr); + } + // if this fails then try the original + if (OldSpawner.m_SetPropertyItem == null) + { + OldSpawner.m_SetPropertyItem = BaseXmlSpawner.FindItemByName(null, namestr, typestr); + } + if (OldSpawner.m_SetPropertyItem == null) + { + failedsetitemcount++; + if (from != null) + { + from.SendMessage(33, "Failed to initialize SetItemProperty Object '{0}' on ' '{1}' at [{2} {3}] in {4}", + setObjectName, OldSpawner.Name, OldSpawner.Location.X, OldSpawner.Location.Y, OldSpawner.Map); + } + + // log it + try + { + using (StreamWriter op = new StreamWriter("badxml.log", true)) + { + op.WriteLine("# Failed SetItemProperty Object initialization : {0}: Format: ObjectName X Y Z Map SpawnerName Xmlfile", + DateTime.UtcNow); + op.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}", + setObjectName, OldSpawner.Location.X, OldSpawner.Location.Y, OldSpawner.Location.Z, OldSpawner.Map, OldSpawner.Name, filename); + op.WriteLine(); + } + } + catch { } + } + } + + string triggerObjectName = null; + try { triggerObjectName = (string)dr["ObjectPropertyItemName"]; } + catch { } + + if (!string.IsNullOrEmpty(triggerObjectName)) + { + string[] typeargs = triggerObjectName.Split(",".ToCharArray(), 2); + string typestr = null; + string namestr = triggerObjectName; + + if (typeargs.Length > 1) + { + namestr = typeargs[0]; + typestr = typeargs[1]; + } + + // if this is a new load then assume that it will be referring to another newly loaded object so append the newloadid + if (loadnew) + { + string tmptriggerObjectName = string.Format("{0}-{1}", namestr, newloadid); + OldSpawner.m_ObjectPropertyItem = BaseXmlSpawner.FindItemByName(null, tmptriggerObjectName, typestr); + } + // if this fails then try the original + if (OldSpawner.m_ObjectPropertyItem == null) + { + OldSpawner.m_ObjectPropertyItem = BaseXmlSpawner.FindItemByName(null, namestr, typestr); + } + if (OldSpawner.m_ObjectPropertyItem == null) + { + failedobjectitemcount++; + if (from != null) + { + from.SendMessage(33, "Failed to initialize TriggerObject '{0}' on ' '{1}' at [{2} {3}] in {4}", + triggerObjectName, OldSpawner.Name, OldSpawner.Location.X, OldSpawner.Location.Y, OldSpawner.Map); + } + + // log it + try + { + using (StreamWriter op = new StreamWriter("badxml.log", true)) + { + op.WriteLine("# Failed TriggerObject initialization : {0}: Format: ObjectName X Y Z Map SpawnerName Xmlfile", + DateTime.UtcNow); + op.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}", + triggerObjectName, OldSpawner.Location.X, OldSpawner.Location.Y, OldSpawner.Location.Z, OldSpawner.Map, OldSpawner.Name, filename); + op.WriteLine(); + } + } + catch { } + } + } + } + } + } + } + + // close the file + try + { + fs.Close(); + } + catch { } + + if (from != null) + { + from.SendMessage("{0} spawner(s) were created from file {1} [Trammel={2}, Felucca={3}, Ilshenar={4}, Malas={5}, Tokuno={6} Other={7}].", + TotalCount, filename, TrammelCount, FeluccaCount, IlshenarCount, MalasCount, TokunoCount, OtherCount); + } + + if (failedobjectitemcount > 0) + { + if (from != null) + { + from.SendMessage(33, "Failed to initialize TriggerObjects in {0} spawners. Saved to 'badxml.log'", failedobjectitemcount); + } + } + if (failedsetitemcount > 0) + { + if (from != null) + { + from.SendMessage(33, "Failed to initialize SetItemProperty Objects in {0} spawners. Saved to 'badxml.log'", failedsetitemcount); + } + } + if (badcount > 0) + { + if (from != null) + { + from.SendMessage(33, "{0} bad spawners detected. Saved to 'badxml.log'", badcount); + } + } + if (questionablecount > 0) + { + if (from != null) + { + from.SendMessage(33, "{0} questionable spawners detected. Saved to 'badxml.log'", questionablecount); + } + } + processedmaps = 1; + processedspawners = TotalCount; + + } + + public static string LocateFile(string filename) + { + bool found = false; + + string dirname = null; + + if (Directory.Exists(XmlSpawnDir)) + { + // get it from the defaults directory if it exists + dirname = string.Format("{0}/{1}", XmlSpawnDir, filename); + found = File.Exists(dirname) || Directory.Exists(dirname); + } + + if (!found) + { + // otherwise just get it from the main installation dir + dirname = filename; + } + + return dirname; + } + + [Usage("XmlNewLoad [SpawnerPrefixFilter]")] + [Description("Loads new XmlSpawner objects with new GUIDs (no replacement) into the current map of the player.")] + public static void NewLoad_OnCommand(CommandEventArgs e) + { + if (e.Mobile.AccessLevel >= DiskAccessLevel) + { + if (e.Arguments.Length >= 1) + { + string filename = LocateFile(e.Arguments[0]); + + // Spawner load criteria (if any) + string SpawnerPrefix = string.Empty; + + // Check if there is an argument provided (load criteria) + if (e.Arguments.Length > 1) + { + SpawnerPrefix = e.Arguments[1]; + } + + int processedmaps; + int processedspawners; + + XmlLoadFromFile(filename, SpawnerPrefix, e.Mobile, false, 0, true, out processedmaps, out processedspawners); + } + else + { + e.Mobile.SendMessage("Usage: {0} [SpawnerPrefixFilter]", e.Command); + } + } + else + { + e.Mobile.SendMessage("You do not have rights to perform this command."); + } + } + + [Usage("XmlLoad [SpawnerPrefixFilter]")] + [Description("Loads XmlSpawner objects (replacing existing spawners with matching GUIDs) into the proper map as defined in the file supplied.")] + public static void Load_OnCommand(CommandEventArgs e) + { + var m = e.Mobile; + + if (m == null || m.AccessLevel >= DiskAccessLevel) + { + if (e.Arguments.Length >= 1) + { + string filename = LocateFile(e.Arguments[0]); + + // Spawner load criteria (if any) + string SpawnerPrefix = string.Empty; + + // Check if there is an argument provided (load criteria) + if (e.Arguments.Length > 1) + { + SpawnerPrefix = e.Arguments[1]; + } + + int processedmaps; + int processedspawners; + + XmlLoadFromFile(filename, SpawnerPrefix, m, false, 0, false, out processedmaps, out processedspawners); + } + else if (m != null) + { + e.Mobile.SendMessage("Usage: {0} [SpawnerPrefixFilter]", e.Command); + } + } + else + { + e.Mobile.SendMessage("You do not have rights to perform this command."); + } + } + + [Usage("XmlNewLoadHere [SpawnerPrefixFilter][-maxrange range]")] + [Description("Loads new XmlSpawner objects with new GUIDs (no replacement) to the current map and location of the player. Spawners beyond maxrange (default=48 tiles) are not moved relative to the player")] + public static void NewLoadHere_OnCommand(CommandEventArgs e) + { + if (e.Mobile.AccessLevel >= DiskAccessLevel) + { + if (e.Arguments.Length >= 1) + { + string filename = LocateFile(e.Arguments[0]); + + // Spawner load criteria (if any) + string SpawnerPrefix = string.Empty; + bool badargs = false; + int maxrange = 48; + + // Check if there is an argument provided (load criteria) + try + { + // Check if there is an argument provided (load criteria) + for (int nxtarg = 1; nxtarg < e.Arguments.Length; nxtarg++) + { + // is it a maxrange option? + if (e.Arguments[nxtarg].ToLower() == "-maxrange") + { + maxrange = int.Parse(e.Arguments[++nxtarg]); + } + else + { + SpawnerPrefix = e.Arguments[nxtarg]; + } + } + } + catch { e.Mobile.SendMessage("Usage: {0} [SpawnerPrefixFilter][-maxrange range]", e.Command); badargs = true; } + + if (!badargs) + { + int processedmaps; + int processedspawners; + + XmlLoadFromFile(filename, SpawnerPrefix, e.Mobile, true, maxrange, true, out processedmaps, out processedspawners); + } + } + else + { + e.Mobile.SendMessage("Usage: {0} [SpawnerPrefixFilter][-maxrange range]", e.Command); + } + } + else + { + e.Mobile.SendMessage("You do not have rights to perform this command."); + } + } + + [Usage("XmlLoadHere [SpawnerPrefixFilter][-maxrange range]")] + [Description("Loads XmlSpawner objects to the current map and location of the player. Spawners beyond maxrange (default=48 tiles) are not moved relative to the player")] + public static void LoadHere_OnCommand(CommandEventArgs e) + { + if (e.Mobile.AccessLevel >= DiskAccessLevel) + { + if (e.Arguments.Length >= 1) + { + string filename = LocateFile(e.Arguments[0]); + + // Spawner load criteria (if any) + string SpawnerPrefix = string.Empty; + bool badargs = false; + int maxrange = 48; + + try + { + // Check if there is an argument provided (load criteria) + for (int nxtarg = 1; nxtarg < e.Arguments.Length; nxtarg++) + { + // is it a maxrange option? + if (e.Arguments[nxtarg].ToLower() == "-maxrange") + { + maxrange = int.Parse(e.Arguments[++nxtarg]); + } + else + { + SpawnerPrefix = e.Arguments[nxtarg]; + } + } + } + catch { e.Mobile.SendMessage("Usage: {0} [SpawnerPrefixFilter][-maxrange range]", e.Command); badargs = true; } + + if (!badargs) + { + int processedmaps; + int processedspawners; + + XmlLoadFromFile(filename, SpawnerPrefix, e.Mobile, true, maxrange, false, out processedmaps, out processedspawners); + } + } + else + { + e.Mobile.SendMessage("Usage: {0} [SpawnerPrefixFilter][-maxrange range]", e.Command); + } + } + else + { + e.Mobile.SendMessage("You do not have rights to perform this command."); + } + } + + [Usage("XmlSaveOld [SpawnerPrefixFilter]")] + [Description("Saves all XmlSpawner objects from the current map into the file supplied in the old xmlspawner format.")] + public static void SaveOld_OnCommand(CommandEventArgs e) + { + SaveSpawns(e, false, true); + } + + [Usage("XmlSpawnerSave [SpawnerPrefixFilter]")] + [Description("Saves all XmlSpawner objects from the current map into the file supplied.")] + public static void Save_OnCommand(CommandEventArgs e) + { + SaveSpawns(e, false, false); + } + + [Usage("XmlSpawnerSaveAll [SpawnerPrefixFilter]")] + [Description("Saves ALL XmlSpawner objects from the entire world into the file supplied.")] + public static void SaveAll_OnCommand(CommandEventArgs e) + { + SaveSpawns(e, true, false); + } + + public class XmlSaveSingle : BaseCommand + { + public XmlSaveSingle() + { + AccessLevel = DiskAccessLevel; + Supports = CommandSupport.Single; + Commands = new[] { "XmlSaveSingle" }; + ObjectTypes = ObjectTypes.Items; + Usage = "XmlSaveSingle "; + Description = "Saves single xmlspawner to specified file."; + } + + public override void Execute(CommandEventArgs e, object obj) + { + if (e == null || e.Mobile == null || e.Arguments == null) + { + return; + } + + if (e.Arguments.Length < 1) + { + e.Mobile.SendMessage("Usage: {0} (without spaces!!)", e.Command); + return; + } + + string filename = e.Arguments[0]; + + XmlSpawner xmlspawner = obj as XmlSpawner; + + if (xmlspawner == null) + { + e.Mobile.SendMessage("You can select only XmlSpawner objects!"); + return; + } + + Mobile m = e.Mobile; + + CommandLogging.WriteLine(m, "{0} {1} Saving XmlSpawner {2} on file {3}", m.AccessLevel, CommandLogging.Format(m), CommandLogging.Format(xmlspawner), CommandLogging.Format(filename)); + SaveSpawns(m, xmlspawner, filename); + } + } + + private static void SaveSpawns(Mobile m, XmlSpawner xmlspawner, string filename) + { + if (m.AccessLevel < DiskAccessLevel) + { + m.SendMessage("You do not have rights to perform this command."); + return; + } + + string dirname; + + if (Directory.Exists(XmlSpawnDir) && filename != null && !filename.StartsWith("/") && !filename.StartsWith("\\")) + { + // put it in the defaults directory if it exists + dirname = string.Format("{0}/{1}", XmlSpawnDir, filename); + } + else + { + // otherwise just put it in the main installation dir + dirname = filename; + } + + m.SendMessage("Saving object in folder {0} - file {1} - spawner {2}.", dirname, filename, xmlspawner); + + List saveslist = new List(1); + saveslist.Add(xmlspawner); + SaveSpawnList(m, saveslist, dirname, false, true); + } + + private static void SaveSpawns(CommandEventArgs e, bool SaveAllMaps, bool oldformat) + { + if (e == null || e.Mobile == null || e.Arguments == null || e.Arguments.Length < 1) + { + return; + } + + if (e.Mobile.AccessLevel < DiskAccessLevel) + { + e.Mobile.SendMessage("You do not have rights to perform this command."); + return; + } + + if (e.Arguments != null && e.Arguments.Length < 1) + { + e.Mobile.SendMessage("Usage: {0} [SpawnerPrefixFilter]", e.Command); + return; + } + + // Spawner save criteria (if any) + string SpawnerPrefix = string.Empty; + + // Check if there is an argument provided (save criteria) + if (e.Arguments.Length > 1) + { + SpawnerPrefix = e.Arguments[1]; + } + + string filename = e.Arguments[0]; + + string dirname; + if (Directory.Exists(XmlSpawnDir) && filename != null && !filename.StartsWith("/") && !filename.StartsWith("\\")) + { + // put it in the defaults directory if it exists + dirname = string.Format("{0}/{1}", XmlSpawnDir, filename); + } + else + { + // otherwise just put it in the main installation dir + dirname = filename; + } + + if (SaveAllMaps) + { + e.Mobile.SendMessage(string.Format("Saving {0} objects{1} to file {2} from {3}.", "XmlSpawner", + !string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" : string.Empty, dirname, e.Mobile.Map)); + } + else + { + e.Mobile.SendMessage(string.Format("Saving {0} obejcts{1} to file {2} from the entire world.", "XmlSpawner", + !string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" : string.Empty, dirname)); + } + + + List saveslist = new List(); + + // Add each spawn point to the list + foreach (Item i in World.Items.Values) + { + if (i is XmlSpawner spawner && !spawner.Deleted && (SaveAllMaps || spawner.Map == e.Mobile.Map) + //check for mob carried spawners and ignore them + && !(spawner.RootParent is Mobile) + && (SpawnerPrefix == null || SpawnerPrefix.Length == 0 || spawner.Name != null && spawner.Name.StartsWith(SpawnerPrefix))) + { + saveslist.Add(spawner); + } + } + + // save the list + SaveSpawnList(e.Mobile, saveslist, dirname, oldformat, true); + } + + public static bool SaveSpawnList(List savelist, Stream stream) => SaveSpawnList(null, savelist, null, stream, false, false); + + public static bool SaveSpawnList(Mobile from, List savelist, string dirname, bool oldformat, bool verbose) + { + if (string.IsNullOrEmpty(dirname)) + { + return false; + } + + + bool save_ok = true; + FileStream fs = null; + + try + { + // Create the FileStream to write with. + fs = new FileStream(dirname, FileMode.Create); + } + catch + { + if (from != null) + { + from.SendMessage("Error creating file {0}", dirname); + } + + save_ok = false; + } + + // so far so good + if (save_ok) + { + save_ok = SaveSpawnList(from, savelist, dirname, fs, oldformat, verbose); + } + + if (!save_ok && from != null) + { + from.SendMessage("Unable to complete save operation."); + } + + return save_ok; + } + + + public static bool SaveSpawnList(Mobile from, List savelist, string dirname, Stream stream, bool oldformat, bool verbose) + { + if (savelist == null || stream == null) + { + return false; + } + + int TotalCount = 0; + int TrammelCount = 0; + int FeluccaCount = 0; + int IlshenarCount = 0; + int MalasCount = 0; + int TokunoCount = 0; + int OtherCount = 0; + + + // Create the data set + DataSet ds = new DataSet(SpawnDataSetName); + + // Load the data set up + ds.Tables.Add(SpawnTablePointName); + + // Create spawn point schema + ds.Tables[SpawnTablePointName].Columns.Add("Name"); + ds.Tables[SpawnTablePointName].Columns.Add("UniqueId"); + ds.Tables[SpawnTablePointName].Columns.Add("Map"); + ds.Tables[SpawnTablePointName].Columns.Add("X"); + ds.Tables[SpawnTablePointName].Columns.Add("Y"); + ds.Tables[SpawnTablePointName].Columns.Add("Width"); + ds.Tables[SpawnTablePointName].Columns.Add("Height"); + ds.Tables[SpawnTablePointName].Columns.Add("CentreX"); + ds.Tables[SpawnTablePointName].Columns.Add("CentreY"); + ds.Tables[SpawnTablePointName].Columns.Add("CentreZ"); + ds.Tables[SpawnTablePointName].Columns.Add("Range"); + ds.Tables[SpawnTablePointName].Columns.Add("MaxCount"); + ds.Tables[SpawnTablePointName].Columns.Add("MinDelay"); + ds.Tables[SpawnTablePointName].Columns.Add("MaxDelay"); + // deal with the double format for delay. old format stored them as minutes in int format. that meant that short delays were lost + // proper solution would simply be to store as doubles, but older progs still assume int format (like spawneditor) + // so this is the solution. add a flag and do it both ways. + ds.Tables[SpawnTablePointName].Columns.Add("DelayInSec"); + + // add the duration and proximity range and sound parameters, and in container flag and coords inside the container + ds.Tables[SpawnTablePointName].Columns.Add("Duration"); + ds.Tables[SpawnTablePointName].Columns.Add("DespawnTime"); + ds.Tables[SpawnTablePointName].Columns.Add("ProximityRange"); + ds.Tables[SpawnTablePointName].Columns.Add("ProximityTriggerSound"); + ds.Tables[SpawnTablePointName].Columns.Add("ProximityTriggerMessage"); + ds.Tables[SpawnTablePointName].Columns.Add("ObjectPropertyName"); + ds.Tables[SpawnTablePointName].Columns.Add("ObjectPropertyItemName"); + ds.Tables[SpawnTablePointName].Columns.Add("SetPropertyItemName"); + ds.Tables[SpawnTablePointName].Columns.Add("ItemTriggerName"); + ds.Tables[SpawnTablePointName].Columns.Add("NoItemTriggerName"); + ds.Tables[SpawnTablePointName].Columns.Add("MobTriggerName"); + ds.Tables[SpawnTablePointName].Columns.Add("MobPropertyName"); + ds.Tables[SpawnTablePointName].Columns.Add("PlayerPropertyName"); + ds.Tables[SpawnTablePointName].Columns.Add("TriggerProbability"); + ds.Tables[SpawnTablePointName].Columns.Add("SpeechTrigger"); + ds.Tables[SpawnTablePointName].Columns.Add("SkillTrigger"); + ds.Tables[SpawnTablePointName].Columns.Add("InContainer"); + ds.Tables[SpawnTablePointName].Columns.Add("ContainerX"); + ds.Tables[SpawnTablePointName].Columns.Add("ContainerY"); + ds.Tables[SpawnTablePointName].Columns.Add("ContainerZ"); + ds.Tables[SpawnTablePointName].Columns.Add("MinRefractory"); + ds.Tables[SpawnTablePointName].Columns.Add("MaxRefractory"); + ds.Tables[SpawnTablePointName].Columns.Add("TODStart"); + ds.Tables[SpawnTablePointName].Columns.Add("TODEnd"); + ds.Tables[SpawnTablePointName].Columns.Add("TODMode"); + ds.Tables[SpawnTablePointName].Columns.Add("KillReset"); + ds.Tables[SpawnTablePointName].Columns.Add("ExternalTriggering"); + ds.Tables[SpawnTablePointName].Columns.Add("SequentialSpawning"); + ds.Tables[SpawnTablePointName].Columns.Add("RegionName"); + ds.Tables[SpawnTablePointName].Columns.Add("AllowGhostTriggering"); + ds.Tables[SpawnTablePointName].Columns.Add("AllowNPCTriggering"); + ds.Tables[SpawnTablePointName].Columns.Add("SpawnOnTrigger"); + ds.Tables[SpawnTablePointName].Columns.Add("ConfigFile"); + ds.Tables[SpawnTablePointName].Columns.Add("SmartSpawning"); + ds.Tables[SpawnTablePointName].Columns.Add("TickReset"); + + ds.Tables[SpawnTablePointName].Columns.Add("WayPoint"); + ds.Tables[SpawnTablePointName].Columns.Add("Team"); + // amount for stacked item spawns + ds.Tables[SpawnTablePointName].Columns.Add("Amount"); + ds.Tables[SpawnTablePointName].Columns.Add("IsGroup"); + ds.Tables[SpawnTablePointName].Columns.Add("IsRunning"); + ds.Tables[SpawnTablePointName].Columns.Add("IsHomeRangeRelative"); + ds.Tables[SpawnTablePointName].Columns.Add(oldformat ? "Objects" : "Objects2"); + + // Always export sorted by UUID to help diffs + savelist.Sort((a, b) => + { + return a.UniqueId.CompareTo(b.UniqueId); + }); + + // Add each spawn point to the new table + foreach (XmlSpawner sp in savelist) + { + if (sp == null || sp.Map == null || sp.Deleted) + { + continue; + } + + if (verbose && from != null) + // Send a message to the client that the spawner is being saved + { + from.SendMessage(68, "Saving '{0}' in {1} at {2}", sp.Name, sp.Map.Name, sp.Location.ToString()); + } + + // Create a new data row + DataRow dr = ds.Tables[SpawnTablePointName].NewRow(); + + // Populate the data + dr["Name"] = sp.Name; + + // Set the unqiue id + dr["UniqueId"] = sp.m_UniqueId; + + // Get the map name + dr["Map"] = sp.Map.Name; + + // Convert the xml map value to a real map object + if (string.Compare(sp.Map.Name, Map.Trammel.Name, true) == 0) + { + TrammelCount++; + } + else if (string.Compare(sp.Map.Name, Map.Felucca.Name, true) == 0) + { + FeluccaCount++; + } + else if (string.Compare(sp.Map.Name, Map.Ilshenar.Name, true) == 0) + { + IlshenarCount++; + } + else if (string.Compare(sp.Map.Name, Map.Malas.Name, true) == 0) + { + MalasCount++; + } + else if (string.Compare(sp.Map.Name, Map.Tokuno.Name, true) == 0) + { + TokunoCount++; + } + else + { + OtherCount++; + } + + dr["X"] = sp.m_X; + dr["Y"] = sp.m_Y; + dr["Width"] = sp.m_Width; + dr["Height"] = sp.m_Height; + + // check to see if this is in a container + if (sp.RootParent is Container container) + { + dr["CentreX"] = container.Location.X; + dr["CentreY"] = container.Location.Y; + dr["CentreZ"] = container.Location.Z; + dr["ContainerX"] = sp.Location.X; + dr["ContainerY"] = sp.Location.Y; + dr["ContainerZ"] = sp.Location.Z; + dr["InContainer"] = true; + } + else + { + dr["CentreX"] = sp.Location.X; + dr["CentreY"] = sp.Location.Y; + dr["CentreZ"] = sp.Location.Z; + //dr["ContainerX"] = 0; + //dr["ContainerY"] = 0; + //dr["ContainerZ"] = 0; + dr["InContainer"] = false; + } + dr["Range"] = sp.m_HomeRange; + dr["MaxCount"] = sp.m_Count; + + // need to deal with the fact that the old xmlspawner xml format only saved delays in minutes as ints, so shorter spawn times + // are lost + // flag it then on reading it can be properly handled and still + // maintain backward compatibility with older xml files + if ((int)sp.m_MinDelay.TotalSeconds - 60 * (int)sp.m_MinDelay.TotalMinutes > 0 || + (int)sp.m_MaxDelay.TotalSeconds - 60 * (int)sp.m_MaxDelay.TotalMinutes > 0) + { + dr["DelayInSec"] = true; + dr["MinDelay"] = (int)sp.m_MinDelay.TotalSeconds; + dr["MaxDelay"] = (int)sp.m_MaxDelay.TotalSeconds; + } + else + { + dr["DelayInSec"] = false; + dr["MinDelay"] = (int)sp.m_MinDelay.TotalMinutes; + dr["MaxDelay"] = (int)sp.m_MaxDelay.TotalMinutes; + } + + // additional parameters + dr["TODStart"] = sp.m_TODStart.TotalMinutes; + dr["TODEnd"] = sp.m_TODEnd.TotalMinutes; + dr["TODMode"] = (int)sp.m_TODMode; + dr["KillReset"] = sp.m_KillReset; + dr["MinRefractory"] = sp.m_MinRefractory.TotalMinutes; + dr["MaxRefractory"] = sp.m_MaxRefractory.TotalMinutes; + dr["Duration"] = sp.m_Duration.TotalMinutes; + dr["DespawnTime"] = sp.m_DespawnTime.TotalHours; + dr["ExternalTriggering"] = sp.m_ExternalTriggering; + + dr["ProximityRange"] = sp.m_ProximityRange; + dr["ProximityTriggerSound"] = sp.m_ProximityTriggerSound; + dr["ProximityTriggerMessage"] = sp.m_ProximityTriggerMessage; + if (sp.m_ObjectPropertyItem != null && !sp.m_ObjectPropertyItem.Deleted) + { + dr["ObjectPropertyItemName"] = string.Format("{0},{1}", sp.m_ObjectPropertyItem.Name, + sp.m_ObjectPropertyItem.GetType().Name); + } + else + { + dr["ObjectPropertyItemName"] = null; + } + + dr["ObjectPropertyName"] = sp.m_ObjectPropertyName; + if (sp.m_SetPropertyItem != null && !sp.m_SetPropertyItem.Deleted) + { + dr["SetPropertyItemName"] = string.Format("{0},{1}", sp.m_SetPropertyItem.Name, + sp.m_SetPropertyItem.GetType().Name); + } + else + { + dr["SetPropertyItemName"] = null; + } + + dr["ItemTriggerName"] = sp.m_ItemTriggerName; + dr["NoItemTriggerName"] = sp.m_NoItemTriggerName; + dr["MobTriggerName"] = sp.m_MobTriggerName; + dr["MobPropertyName"] = sp.m_MobPropertyName; + dr["PlayerPropertyName"] = sp.m_PlayerPropertyName; + dr["TriggerProbability"] = sp.m_TriggerProbability; + dr["SequentialSpawning"] = sp.m_SequentialSpawning; + dr["RegionName"] = sp.m_RegionName; + dr["AllowGhostTriggering"] = sp.m_AllowGhostTriggering; + dr["AllowNPCTriggering"] = sp.m_AllowNPCTriggering; + dr["SpawnOnTrigger"] = sp.m_SpawnOnTrigger; + dr["ConfigFile"] = sp.m_ConfigFile; + dr["SmartSpawning"] = sp.m_SmartSpawning; + dr["TickReset"] = sp.m_DisableGlobalAutoReset; + + dr["SpeechTrigger"] = sp.m_SpeechTrigger; + dr["SkillTrigger"] = sp.m_SkillTrigger; + dr["Amount"] = sp.m_StackAmount; + dr["Team"] = sp.m_Team; + + // assign the waypoint based on the waypoint name if it deviates from the default waypoint name, otherwise do it by serial + string waystr = null; + if (sp.m_WayPoint != null) + { + if (sp.m_WayPoint.Name != defwaypointname && !string.IsNullOrEmpty(sp.m_WayPoint.Name)) + { + waystr = sp.m_WayPoint.Name; + } + else + { + waystr = string.Format("SERIAL,{0}", sp.m_WayPoint.Serial); + } + } + dr["WayPoint"] = waystr; + + dr["IsGroup"] = sp.m_Group; + dr["IsRunning"] = sp.m_Running; + dr["IsHomeRangeRelative"] = sp.m_HomeRangeIsRelative; + if (oldformat) + { + dr["Objects"] = sp.GetSerializedObjectList(); + } + else + { + dr["Objects2"] = sp.GetSerializedObjectList2(); + } + + // Add the row the the table + ds.Tables[SpawnTablePointName].Rows.Add(dr); + + // Increment the count + TotalCount++; + } + + // Write out the file + bool file_error = false; + if (TotalCount > 0) + { + try + { + ds.WriteXml(stream); + } + catch { file_error = true; } + + if (file_error) + { + return false; + } + } + + try + { + stream.Close(); + } + catch { } + // Indicate how many spawners were written + if (from != null) + { + from.SendMessage("{0} spawner(s) were saved to file {1} [Trammel={2}, Felucca={3}, Ilshenar={4}, Malas={5}, Tokuno={6}, Other={7}].", + TotalCount, dirname, TrammelCount, FeluccaCount, IlshenarCount, MalasCount, TokunoCount, OtherCount); + } + return true; + + } + + private static void WipeSpawners(CommandEventArgs e, bool WipeAll) + { + if (e == null || e.Mobile == null) + { + return; + } + + if (e.Mobile.AccessLevel >= AccessLevel.Administrator) + { + // Spawner delete criteria (if any) + string SpawnerPrefix = string.Empty; + + // Check if there is an argument provided (delete criteria) + if (e.Arguments != null && e.Arguments.Length > 0) + { + SpawnerPrefix = e.Arguments[0]; + } + + if (WipeAll) + { + e.Mobile.SendMessage("Removing ALL XmlSpawner objects from the world{0}.", !string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" + : string.Empty); + } + else + { + e.Mobile.SendMessage("Removing ALL XmlSpawner objects from {0}{1}.", e.Mobile.Map, !string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" + : string.Empty); + } + + // Delete Xml spawner's in the world based on the mobiles current map + int Count = 0; + List ToDelete = new List(); + foreach (Item i in World.Items.Values) + { + if (i is XmlSpawner && (WipeAll || i.Map == e.Mobile.Map) && i.Deleted == false) + { + // Check if there is a delete condition + if (SpawnerPrefix == null || SpawnerPrefix.Length == 0 || i.Name.StartsWith(SpawnerPrefix)) + { + // Send a message to the client that the spawner is being deleted + //e.Mobile.SendMessage(33, "Removing '{0}' in {1} at {2}", i.Name, i.Map.Name, i.Location.ToString()); + + ToDelete.Add(i); + Count++; + } + } + } + + // Delete the items in the array list + foreach (Item i in ToDelete) + { + i.Delete(); + } + + if (WipeAll) + { + e.Mobile.SendMessage("Removed {0} XmlSpawner objects from the world.", Count); + } + else + { + e.Mobile.SendMessage("Removed {0} XmlSpawner objects from {1}.", Count, e.Mobile.Map); + } + } + else + { + e.Mobile.SendMessage("You do not have rights to perform this command."); + } + } + + + [Usage("XmlSpawnerRespawn [SpawnerPrefixFilter]")] + [Description("Respawns all XmlSpawner objects from the current map.")] + public static void Respawn_OnCommand(CommandEventArgs e) + { + RespawnSpawners(e, false); + } + + [Usage("XmlSpawnerRespawnAll [SpawnerPrefixFilter]")] + [Description("Respawns all XmlSpawner objects from the entire world.")] + public static void RespawnAll_OnCommand(CommandEventArgs e) + { + RespawnSpawners(e, true); + } + + private static void RespawnSpawners(CommandEventArgs e, bool RespawnAll) + { + if (e == null || e.Mobile == null) + { + return; + } + + if (e.Mobile.AccessLevel >= AccessLevel.Administrator) + { + // Spawner Respawn criteria (if any) + string SpawnerPrefix = string.Empty; + + // Check if there is an argument provided (respawn criteria) + if (e.Arguments != null && e.Arguments.Length > 0) + { + SpawnerPrefix = e.Arguments[0]; + } + + if (RespawnAll) + { + e.Mobile.SendMessage("Respawning ALL XmlSpawner objects from the world{0}.", !string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" + : string.Empty); + } + else + { + e.Mobile.SendMessage("Respawning ALL XmlSpawner objects from {0}{1}.", e.Mobile.Map, !string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" + : string.Empty); + } + + // Respawn Xml spawner's in the world based on the mobiles current map + int Count = 0; + List ToRespawn = new List(); + foreach (Item i in World.Items.Values) + { + try + { + + if (i is XmlSpawner && (RespawnAll || i.Map == e.Mobile.Map) && i.Deleted == false) + { + // Check if there is a respawn condition + if (SpawnerPrefix == null || SpawnerPrefix.Length == 0 || i.Name != null && i.Name.StartsWith(SpawnerPrefix)) + { + ToRespawn.Add(i); + Count++; + } + } + } + catch (Exception ex) { Console.WriteLine("Error attempting to add {0}, {1}", i, ex.Message); } + } + // Respawn the items in the array list + foreach (Item i in ToRespawn) + { + + // Send a message to the client that the spawner is being respawned + e.Mobile.SendMessage(33, "Respawning '{0}' in {1} at {2}", i.Name, i.Map.Name, i.Location.ToString()); + XmlSpawner CheckXmlSpawner = (XmlSpawner)i; + CheckXmlSpawner.TryRespawn(); + } + + if (RespawnAll) + { + e.Mobile.SendMessage("Respawned {0} XmlSpawner objects from the world.", Count); + } + else + { + e.Mobile.SendMessage("Respawned {0} XmlSpawner objects from {1}.", Count, e.Mobile.Map); + } + } + else + { + e.Mobile.SendMessage("You do not have rights to perform this command."); + } + } + +#if (TRACE) + public static void XmlMake_OnCommand(CommandEventArgs e) + { + + if (e.Arguments.Length > 0) + { + int count = 0; + try + { + count = Convert.ToInt32(e.Arguments[0], 10); + } + catch (Exception ex) { Diagnostics.ExceptionLogging.LogException(ex); } + + for (int i = 0; i < count; i++) + { + if (e.Arguments.Length > 2) + { + Spawner x = new Spawner(10, 1, 1, 0, 2, e.Arguments[1]); + x.Location = new Point3D(5400 + Utility.Random(700), 1090 + Utility.Random(180), 0); + x.Map = Map.Trammel; + } + else + if (e.Arguments.Length > 1) + { + XmlSpawner x = new XmlSpawner(10, 1, 1, 0, 2, e.Arguments[1]); + x.Location = new Point3D(5400 + Utility.Random(700), 1090 + Utility.Random(180), 0); + x.Map = Map.Trammel; + //x.MinDelay = TimeSpan.FromSeconds(1); + //x.MaxDelay = TimeSpan.FromSeconds(1); + //x.ProximityRange = 0; + } + } + if (e.Arguments.Length > 2) + { + e.Mobile.SendMessage("Created {0} Spawner objects.", count); + } + else + { + e.Mobile.SendMessage("Created {0} XmlSpawner objects.", count); + } + + + } + } + + public static void XmlTrace_OnCommand() + { + XmlTrace_OnCommand(null); + } + + public static void XmlTrace_OnCommand(CommandEventArgs e) + { + Process currentprocess = Process.GetCurrentProcess(); + TimeSpan runningtime = DateTime.UtcNow - _traceStartTime; + double processtime = currentprocess.UserProcessorTime.TotalMilliseconds - _startProcessTime; + double sysload = 0; + + if (runningtime.TotalMilliseconds > 0) + { + sysload = processtime / runningtime.TotalMilliseconds; + } + + Console.WriteLine("______________"); + Console.WriteLine("Active Traces:"); + Console.WriteLine("Running Time = {0}", runningtime); + Console.WriteLine("Adjusted Process Time = {0:####.####} secs", processtime / 1000); + Console.WriteLine("Processor Time = {0} ({1:p3} avg sys load)", currentprocess.UserProcessorTime, sysload); + + for (int i = 0; i < MaxTraces; i++) + { + if (_traceCount[i] > 0) + { + double load = 0; + if (processtime > 0) + { + load = _traceTotal[i].TotalMilliseconds / processtime; + } + Console.WriteLine("{0} ({4}) {1,21} / {2} calls = {3:####.####} ms/call, {5:p3}", + i, _traceTotal[i], _traceCount[i], _traceTotal[i].TotalMilliseconds / _traceCount[i], + _traceName[i], load); + } + } + } + + public static void XmlResetTrace_OnCommand(CommandEventArgs e) + { + + if (e.Arguments.Length >= 0) + { + for (int i = 0; i < MaxTraces; i++) + { + _traceCount[i] = 0; + _traceTotal[i] = TimeSpan.Zero; + } + _traceStartTime = DateTime.UtcNow; + + Process currentprocess = Process.GetCurrentProcess(); + _startProcessTime = currentprocess.UserProcessorTime.TotalMilliseconds; + + Console.WriteLine("Traces reset"); + } + } +#endif + + [Constructible] + public XmlSpawner() + : base(BaseItemId) + { + m_PlayerCreated = true; + m_UniqueId = Guid.NewGuid().ToString(); + SpawnRange = defSpawnRange; + + InitSpawn(0, 0, m_Width, m_Height, string.Empty, 0, defMinDelay, defMaxDelay, defDuration, + defProximityRange, defProximityTriggerSound, defAmount, defTeam, defHomeRange, defRelativeHome, new SpawnObject[0], defMinRefractory, defMaxRefractory, + defTODStart, defTODEnd, null, null, null, null, null, null, null, null, null, defTriggerProbability, null, defIsGroup, defTODMode, + defKillReset, false, -1, null, false, false, false, null, defDespawnTime, null, false, null); + } + + [Constructible] + public XmlSpawner(int amount, int minDelay, int maxDelay, int team, int homeRange, string creatureName) + : base(BaseItemId) + { + m_PlayerCreated = true; + m_UniqueId = Guid.NewGuid().ToString(); + SpawnRange = homeRange; + SpawnObject[] so = new SpawnObject[1]; + so[0] = new SpawnObject(creatureName, amount); + + InitSpawn(0, 0, m_Width, m_Height, string.Empty, amount, TimeSpan.FromMinutes(minDelay), TimeSpan.FromMinutes(maxDelay), defDuration, + defProximityRange, defProximityTriggerSound, defAmount, team, homeRange, defRelativeHome, so, defMinRefractory, defMaxRefractory, + defTODStart, defTODEnd, null, null, null, null, null, null, null, null, null, defTriggerProbability, null, defIsGroup, defTODMode, + defKillReset, false, -1, null, false, false, false, null, defDespawnTime, null, false, null); + } + + [Constructible] + public XmlSpawner(int amount, int minDelay, int maxDelay, int team, int homeRange, int spawnRange, string creatureName) + : base(BaseItemId) + { + m_PlayerCreated = true; + m_UniqueId = Guid.NewGuid().ToString(); + SpawnRange = spawnRange; + SpawnObject[] so = new SpawnObject[1]; + so[0] = new SpawnObject(creatureName, amount); + + InitSpawn(0, 0, m_Width, m_Height, string.Empty, amount, TimeSpan.FromMinutes(minDelay), TimeSpan.FromMinutes(maxDelay), defDuration, + defProximityRange, defProximityTriggerSound, defAmount, team, homeRange, defRelativeHome, so, defMinRefractory, defMaxRefractory, + defTODStart, defTODEnd, null, null, null, null, null, null, null, null, null, defTriggerProbability, null, defIsGroup, defTODMode, + defKillReset, false, -1, null, false, false, false, null, defDespawnTime, null, false, null); + } + + [Constructible] + public XmlSpawner(string creatureName) + : base(BaseItemId) + { + m_PlayerCreated = true; + m_UniqueId = Guid.NewGuid().ToString(); + SpawnObject[] so = new SpawnObject[1]; + so[0] = new SpawnObject(creatureName, 1); + SpawnRange = defSpawnRange; + + InitSpawn(0, 0, m_Width, m_Height, string.Empty, 1, defMinDelay, defMaxDelay, defDuration, + defProximityRange, defProximityTriggerSound, defAmount, defTeam, defHomeRange, defRelativeHome, so, defMinRefractory, defMaxRefractory, + defTODStart, defTODEnd, null, null, null, null, null, null, null, null, null, defTriggerProbability, null, defIsGroup, defTODMode, + defKillReset, false, -1, null, false, false, false, null, defDespawnTime, null, false, null); + } + + public XmlSpawner(Guid uniqueId, int x, int y, int width, int height, string name, int maxCount, TimeSpan minDelay, TimeSpan maxDelay, TimeSpan duration, + int proximityRange, int proximityTriggerSound, int amount, int team, int homeRange, bool isRelativeHomeRange, SpawnObject[] spawnObjects, + TimeSpan minRefractory, TimeSpan maxRefractory, TimeSpan todstart, TimeSpan todend, Item objectPropertyItem, string objectPropertyName, string proximityMessage, + string itemTriggerName, string noitemTriggerName, string speechTrigger, string mobTriggerName, string mobPropertyName, string playerPropertyName, double triggerProbability, + Item setPropertyItem, bool isGroup, TODModeType todMode, int killReset, bool externalTriggering, int sequentialSpawning, string regionName, + bool allowghost, bool allownpc, bool spawnontrigger, string configfile, TimeSpan despawnTime, string skillTrigger, bool smartSpawning, WayPoint wayPoint) + : base(BaseItemId) + { + m_UniqueId = uniqueId.ToString(); + InitSpawn(x, y, width, height, name, maxCount, minDelay, maxDelay, duration, + proximityRange, proximityTriggerSound, amount, team, homeRange, isRelativeHomeRange, spawnObjects, minRefractory, maxRefractory, todstart, todend, + objectPropertyItem, objectPropertyName, proximityMessage, itemTriggerName, noitemTriggerName, speechTrigger, mobTriggerName, mobPropertyName, playerPropertyName, + triggerProbability, setPropertyItem, isGroup, todMode, killReset, externalTriggering, sequentialSpawning, regionName, allowghost, allownpc, spawnontrigger, configfile, + despawnTime, skillTrigger, smartSpawning, wayPoint); + } + + + public void InitSpawn(int x, int y, int width, int height, string name, int maxCount, TimeSpan minDelay, TimeSpan maxDelay, TimeSpan duration, + int proximityRange, int proximityTriggerSound, int amount, int team, int homeRange, bool isRelativeHomeRange, SpawnObject[] objectsToSpawn, + TimeSpan minRefractory, TimeSpan maxRefractory, TimeSpan todstart, TimeSpan todend, Item objectPropertyItem, string objectPropertyName, string proximityMessage, + string itemTriggerName, string noitemTriggerName, string speechTrigger, string mobTriggerName, string mobPropertyName, string playerPropertyName, double triggerProbability, + Item setPropertyItem, bool isGroup, TODModeType todMode, int killReset, bool externalTriggering, int sequentialSpawning, string regionName, bool allowghost, bool allownpc, bool spawnontrigger, + string configfile, TimeSpan despawnTime, string skillTrigger, bool smartSpawning, WayPoint wayPoint) + { + + Visible = false; + Movable = false; + m_X = x; + m_Y = y; + m_Width = width; + m_Height = height; + + // init spawn range if compatible + if (width == height) + { + m_SpawnRange = width / 2; + } + else + { + m_SpawnRange = -1; + } + + m_Running = true; + m_Group = isGroup; + + Name = !string.IsNullOrEmpty(name) ? name : "Spawner"; + + m_MinDelay = minDelay; + m_MaxDelay = maxDelay; + + // duration and proximity range parameter + m_MinRefractory = minRefractory; + m_MaxRefractory = maxRefractory; + m_TODStart = todstart; + m_TODEnd = todend; + m_TODMode = todMode; + m_KillReset = killReset; + m_Duration = duration; + m_DespawnTime = despawnTime; + m_ProximityRange = proximityRange; + m_ProximityTriggerSound = proximityTriggerSound; + m_proximityActivated = false; + m_durActivated = false; + m_refractActivated = false; + m_Count = maxCount; + m_Team = team; + m_StackAmount = amount; + m_HomeRange = homeRange; + m_HomeRangeIsRelative = isRelativeHomeRange; + m_ObjectPropertyItem = objectPropertyItem; + m_ObjectPropertyName = objectPropertyName; + m_ProximityTriggerMessage = proximityMessage; + m_ItemTriggerName = itemTriggerName; + m_NoItemTriggerName = noitemTriggerName; + m_SpeechTrigger = speechTrigger; + SkillTrigger = skillTrigger; // note this will register the skill as well + m_MobTriggerName = mobTriggerName; + m_MobPropertyName = mobPropertyName; + m_PlayerPropertyName = playerPropertyName; + m_TriggerProbability = triggerProbability; + m_SetPropertyItem = setPropertyItem; + m_ExternalTriggering = externalTriggering; + m_ExternalTrigger = false; + m_SequentialSpawning = sequentialSpawning; + RegionName = regionName; + m_AllowGhostTriggering = allowghost; + m_AllowNPCTriggering = allownpc; + m_SpawnOnTrigger = spawnontrigger; + m_SmartSpawning = smartSpawning; + ConfigFile = configfile; + m_WayPoint = wayPoint; + + // set the totalitem property to -1 so that it doesnt show up in the item count of containers + //TotalItems = -1; + //UpdateTotal(this, TotalType.Items, -1); + + // Create the array of spawned objects + m_SpawnObjects = new List(); + + // Assign the list of objects to spawn + SpawnObjects = objectsToSpawn; + + // Kick off the process + DoTimer(TimeSpan.FromSeconds(1)); + } + + public XmlSpawner(Serial serial) + : base(serial) + { + } + + public void Defrag(bool killtest) + { + if (m_SpawnObjects == null) + { + return; + } + + bool removed = false; + int total_removed = 0; + + List deleteilist = new List(); + List deletemlist = new List(); + foreach (SpawnObject so in m_SpawnObjects) + { + for (int x = 0; x < so.SpawnedObjects.Count; x++) + { + object o = so.SpawnedObjects[x]; + + if (o is Item item) + { + bool despawned = false; + // check to see if the despawn time has elapsed. If so, then delete it if it hasnt been picked up or stolen. + if (DespawnTime.TotalHours > 0 && !item.Deleted && item.LastMoved < DateTime.UtcNow - DespawnTime && item.Parent == Parent + && (!ItemFlags.GetTaken(item) || item.Parent != null && item.Parent == Parent)) // can despawn if just moved within the same container + { + //item.Delete(); + deleteilist.Add(item); + despawned = true; + } + + // Check if the items has been deleted or + // if something else now owns the item (picked it up for example) + // also check the stolen/placed in container flag. If any of those are true then the spawner doesnt own it any more so take it off the list. + // the stolen/container flag prevents spawns from being left on the list when players take them and lock them back down on the ground. + // If you have made the changes to stealing.cs and container.cs described in xmlspawner2.txt then just uncomment the line below to + // enable this check + if (item.Deleted || despawned || item.Parent != Parent // different container + || ItemFlags.GetTaken(item) && (item.Parent == null || item.Parent != Parent)) // taken and in the world, or a different container + { + so.SpawnedObjects.Remove(item); + x--; + removed = true; + // if sequential spawning is active and the RestrictKillsToSubgroup flag is set, then check to see if + // the object is in the current subgroup before adding to the total + if (SequentialSpawn >= 0 && so.RestrictKillsToSubgroup) + { + if (so.SubGroup == SequentialSpawn) + { + total_removed++; + } + } + else + { + // just add it + total_removed++; + } + } + } + else if (o is Mobile mobile) + { + bool despawned = false; + // check to see if the despawn time has elapsed. If so, and the sector is not active then delete it. + if (DespawnTime.TotalHours > 0 && !mobile.Deleted && mobile.Created < DateTime.UtcNow - DespawnTime + && mobile.Map != null && mobile.Map != Map.Internal && !mobile.Map.GetSector(mobile.Location).Active) + { + //m.Delete(); + deletemlist.Add(mobile); + despawned = true; + } + + if (mobile.Deleted || despawned) + { + // Remove the delete mobile from the list + so.SpawnedObjects.Remove(mobile); + x--; + removed = true; + // if sequential spawning is active and the RestrictKillsToSubgroup flag is set, then check to see if + // the object is in the current subgroup before adding to the total + if (SequentialSpawn >= 0 && so.RestrictKillsToSubgroup) + { + if (so.SubGroup == SequentialSpawn) + { + total_removed++; + } + } + else + { + // just add it + total_removed++; + } + } + else if (mobile is BaseCreature creature) + { + // Check if the creature has been tamed or previously tamed and released + // and if it is, remove it from the list of spawns + if (creature.Controlled || creature.IsStabled || creature.Owners != null && creature.Owners.Count > 0) + { + so.SpawnedObjects.Remove(mobile); + x--; + removed = true; + // if sequential spawning is active and the RestrictKillsToSubgroup flag is set, then check to see if + // the object is in the current subgroup before adding to the total + if (SequentialSpawn >= 0 && so.RestrictKillsToSubgroup) + { + if (so.SubGroup == SequentialSpawn) + { + total_removed++; + } + } + else + { + // just add it + total_removed++; + } + } + } + } + else + if (o is BaseXmlSpawner.KeywordTag tag) + { + if (tag.Deleted) + { + so.SpawnedObjects.Remove(o); + x--; + removed = true; + } + } + else + { + // Don't know what this is, so remove it + Console.WriteLine("removing unknown {0} from spawnlist", so); + so.SpawnedObjects.Remove(o); + x--; + removed = true; + } + } + } + + DeleteFromList(deleteilist, deletemlist); + + // Check if anything has been removed + if (removed) + { + InvalidateProperties(); + } + + // increment the killcount based upon the number of items that were removed from the spawnlist (i.e. were spawned but now are gone, presumed killed) + if (killtest) + { + m_killcount += total_removed; + } + } + + // special defrag pass to remove GOTO keyword tags + public void ClearGOTOTags() + { + if (m_SpawnObjects == null) + { + return; + } + + List ToDelete = new List(); + foreach (SpawnObject so in m_SpawnObjects) + { + for (int x = 0; x < so.SpawnedObjects.Count; x++) + { + object o = so.SpawnedObjects[x]; + if (o is BaseXmlSpawner.KeywordTag sot) + { + // clear the tags except for gump and delay tags + if (sot.Type == 2) + { + ToDelete.Add(sot); + so.SpawnedObjects.Remove(o); + x--; + } + + } + } + } + + for (int x = ToDelete.Count - 1; x >= 0; --x) //BaseXmlSpawner.KeywordTag i in ToDelete) + { + BaseXmlSpawner.KeywordTag i = ToDelete[x]; + if (i != null && !i.Deleted) + { + i.Delete(); + } + } + } + + // special defrag pass to remove spawn object tags, which are placeholders for the special keyword spawn spec entries + public void ClearTags(bool all) + { + if (m_SpawnObjects == null) + { + return; + } + + bool removed = false; + List ToDelete = new List(); + foreach (SpawnObject so in m_SpawnObjects) + { + for (int x = 0; x < so.SpawnedObjects.Count; x++) + { + object o = so.SpawnedObjects[x]; + if (o is BaseXmlSpawner.KeywordTag sot) + { + // clear the tags except for gump and delay tags + if (all || (sot.Flags & BaseXmlSpawner.KeywordFlags.Defrag) != 0) + { + ToDelete.Add(sot); + so.SpawnedObjects.Remove(o); + x--; + removed = true; + } + + } + } + } + + for (int x = ToDelete.Count - 1; x >= 0; --x) //each (BaseXmlSpawner.KeywordTag i in ToDelete) + { + BaseXmlSpawner.KeywordTag i = ToDelete[x]; + if (i != null && !i.Deleted) + { + i.Delete(); + } + } + + // full clear of the taglist + if (all) + { + m_KeywordTagList.Clear(); + } + + // Check if anything has been removed + if (removed) + { + InvalidateProperties(); + } + } + + public void DeleteGumpTags() + { + if (m_SpawnObjects == null) + { + return; + } + + bool removed = false; + List ToDelete = new List(); + foreach (SpawnObject so in m_SpawnObjects) + { + for (int x = 0; x < so.SpawnedObjects.Count; x++) + { + object o = so.SpawnedObjects[x]; + if (o is BaseXmlSpawner.KeywordTag sot) + { + // clear the gump tags + if (sot.Type == 1) + { + ToDelete.Add(sot); + so.SpawnedObjects.Remove(o); + x--; + removed = true; + } + } + } + } + + for (int x = ToDelete.Count - 1; x >= 0; --x) //BaseXmlSpawner.KeywordTag i in ToDelete) + { + BaseXmlSpawner.KeywordTag i = ToDelete[x]; + if (i != null && !i.Deleted) + { + i.Delete(); + } + } + + // Check if anything has been removed + if (removed) + { + InvalidateProperties(); + } + } + + public void DeleteTag(BaseXmlSpawner.KeywordTag tag) + { + if (m_SpawnObjects == null) + { + return; + } + + bool removed = false; + List ToDelete = new List(); + foreach (SpawnObject so in m_SpawnObjects) + { + for (int x = 0; x < so.SpawnedObjects.Count; x++) + { + object o = so.SpawnedObjects[x]; + if (o is BaseXmlSpawner.KeywordTag sot) + { + // clear the matching tags + if (sot == tag) + { + ToDelete.Add(sot); + so.SpawnedObjects.Remove(o); + x--; + removed = true; + } + } + } + } + + for (int x = ToDelete.Count - 1; x >= 0; --x) //BaseXmlSpawner.KeywordTag i in ToDelete) + { + BaseXmlSpawner.KeywordTag i = ToDelete[x]; + if (i != null && !i.Deleted) + { + i.Delete(); + } + } + + // Check if anything has been removed + if (removed) + { + InvalidateProperties(); + } + } + + private int SubGroupCount(int sgroup) + { + if (m_SpawnObjects == null) + { + return 0; + } + + int nsub = 0; + for (int i = 0; i < m_SpawnObjects.Count; i++) + { + SpawnObject s = m_SpawnObjects[i]; + + if (s.SubGroup == sgroup) + { + nsub++; + } + } + + return nsub; + } + + private int RandomAvailableSpawnIndex() => + // get spawn indices randomly from all available spawns independent of group + RandomAvailableSpawnIndex(-1); + + // get spawn indices randomly from all available spawns of a group + private int RandomAvailableSpawnIndex(int sgroup) + { + if (m_SpawnObjects == null) + { + return -1; + } + + int maxrange = 0; + List sgrouplist = null; + int totalcount = 0; + // make a pass to determine which subgroups are available for spawning + // by finding any subgroups that do not have available spawns + for (int i = 0; i < m_SpawnObjects.Count; i++) + { + SpawnObject s = m_SpawnObjects[i]; + if (s.SubGroup > 0 && (s.Ignore || s.Disabled)) + { + continue; + } + + totalcount += s.SpawnedObjects.Count; + if (s.SubGroup > 0 && s.SpawnedObjects.Count >= s.MaxCount) + { + // this subgroup is not available so add it to the list + if (sgrouplist == null) + { + sgrouplist = new List(); + } + sgrouplist.Add(s.SubGroup); + } + } + + for (int i = 0; i < m_SpawnObjects.Count; i++) + { + SpawnObject s = m_SpawnObjects[i]; + + if (s.SubGroup > 0 && (s.Ignore || s.Disabled)) + { + continue; + } + + if (s.MaxCount > s.SpawnedObjects.Count && (sgroup < 0 || sgroup == s.SubGroup) + && (sgrouplist == null || !sgrouplist.Contains(s.SubGroup)) && (s.SubGroup <= 0 || SubGroupCount(s.SubGroup) + totalcount <= MaxCount)) + { + // keep track of the number of spawn objects that are not at max (hence available for spawning) + // this will be used to compute the probabilistic weighting function based on the relative + // maxcounts of each entry + maxrange += s.MaxCount; + s.Available = true; + } + else + { + s.Available = false; + } + } + // now generate a random number over the available spawnobjects + // but only if the entire subgroup is available for spawning + // note, subgroup zero is exempt from this check. + if (maxrange > 0) + { + int randindex = Utility.Random(maxrange); + + // and map it into the avail spawns + int currentrange = 0; + for (int i = 0; i < m_SpawnObjects.Count; i++) + { + SpawnObject s = m_SpawnObjects[i]; + if (s.SubGroup > 0 && (s.Ignore || s.Disabled)) + { + continue; + } + + // keep track of the number of spawn objects that are not at max (hence available for spawning) + if (s.Available) + { + // check to see if the random value maps into the range of the current index + if (randindex >= currentrange && randindex < currentrange + s.MaxCount) + { + return i; + } + + currentrange += s.MaxCount; + } + } + + // should never get here + return -1; + } + + // no spawns are available + return -1; + } + + // get spawn indices randomly from all available spawns of a group + private int RandomSpawnIndex(int sgroup) + { + if (m_SpawnObjects == null) + { + return -1; + } + + int avail = 0; + int maxrange = 0; + for (int i = 0; i < m_SpawnObjects.Count; i++) + { + SpawnObject s = m_SpawnObjects[i]; + + // keep track of the number of spawn objects that are not at max (hence available for spawning) + if (sgroup < 0 || sgroup == s.SubGroup) + { + avail++; + maxrange += s.MaxCount; + } + } + // now generate a random number over the available spawnobjects + if (avail > 0 && maxrange > 0) + { + int randindex = Utility.Random(maxrange); + + // and map it into the avail spawns + int currentrange = 0; + + for (int i = 0; i < m_SpawnObjects.Count; i++) + { + SpawnObject s = m_SpawnObjects[i]; + + // keep track of the number of spawn objects that are not at max (hence available for spawning) + if (sgroup < 0 || sgroup == s.SubGroup) + { + if (randindex >= currentrange && randindex < currentrange + s.MaxCount) + { + return i; + } + + currentrange += s.MaxCount; + } + } + + // should never get here + return -1; + } + + // no spawns are available + return -1; + } + + // return the next subgroup in the sequence. + public int NextSequentialIndex(int sgroup) + { + if (m_SpawnObjects == null || m_SpawnObjects.Count == 0) + { + return 0; + } + + int finddirection = 1; + int largergroup = -1; + + //find the next subgroup that is greater than the current one + for (int j = 0; j < m_SpawnObjects.Count; j++) + { + SpawnObject s = m_SpawnObjects[j]; + if (s.SubGroup > 0 && (s.Ignore || s.Disabled)) + { + continue; + } + + int thisgroup = s.SubGroup; + + // start off by finding a subgroup that is larger + if (finddirection == 1) + { + if (thisgroup > sgroup) + { + largergroup = thisgroup; + + // then work backward to find the group that is less than this but still larger than the current + finddirection = -1; + } + } + else + { + if (thisgroup > sgroup && thisgroup < largergroup) + { + largergroup = thisgroup; + + finddirection = -1; + } + } + } + + // if couldnt find one larger, then it is time to wraparound + if (largergroup < 0 && sgroup >= 0) + { + return NextSequentialIndex(-1); + } + + return largergroup; + } + + // returns the spawn index of a spawn entry in the current sequential subgroup + public int GetCurrentAvailableSequentialSpawnIndex(int sgroup) + { + if (sgroup < 0) + { + return -1; + } + + if (m_SpawnObjects == null) + { + return -1; + } + + if (sgroup == 0) + { + return RandomAvailableSpawnIndex(0); + } + + //return the first instance of a spawn object that is an available member of the requested subgroup + for (int j = 0; j < m_SpawnObjects.Count; j++) + { + SpawnObject s = m_SpawnObjects[j]; + + if (s.SubGroup == sgroup && s.MaxCount > s.SpawnedObjects.Count) + { + return j; + } + } + // failed to find any spawn entry of the requested subgroup + return -1; + } + + // returns the spawn index of a spawn entry in the current sequential subgroup + public int GetCurrentSequentialSpawnIndex(int sgroup) + { + if (sgroup < 0) + { + return -1; + } + + if (m_SpawnObjects == null) + { + return -1; + } + + if (sgroup == 0) + { + return RandomSpawnIndex(0); + } + + //return the first instance of a spawn object that is an available member of the requested subgroup + for (int j = 0; j < m_SpawnObjects.Count; j++) + { + if (m_SpawnObjects[j].SubGroup == sgroup) + { + return j; + } + } + // failed to find any spawn entry of the requested subgroup + return -1; + } + + private void SeqResetTo(int sgroup) + { + // check the SequentialResetTo on the subgroup + // cant do resets on subgroup 0 + if (sgroup == 0) + { + return; + } + + // this will get the index of the first spawn entry in the subgroup + // it will have the subgroup timer settings + int spawnindex = GetCurrentSequentialSpawnIndex(sgroup); + + if (spawnindex >= 0) + { + // if it is greater than zero then initiate reset + SpawnObject s = m_SpawnObjects[spawnindex]; + m_SequentialSpawning = s.SequentialResetTo; + + InitiateSequentialReset(sgroup); + + // clear the spawns + //RemoveSpawnObjects(); + ClearSubgroup(s.SubGroup); + + // and reset the kill count + KillCount = 0; + } + } + + private bool CheckForSequentialReset() + { + // check the SequentialResetTime on the subgroup + // cant do resets on subgroup 0 + if (m_SequentialSpawning == 0) + { + return false; + } + + // this will get the index of the first spawn entry in the subgroup + // it will have the subgroup timer settings + int spawnindex = GetCurrentSequentialSpawnIndex(m_SequentialSpawning); + + if (spawnindex >= 0) + { + // check the reset time on it + SpawnObject s = m_SpawnObjects[spawnindex]; + // if it is greater than zero then resetting is possible + if (s.SequentialResetTime > 0) + { + // so check the reset timer + if (NextSeqReset <= TimeSpan.Zero) + { + // it has expired so time to reset + return true; + } + } + } + return false; + } + + private void InitiateSequentialReset(int sgroup) + { + // check the SequentialResetTime on the subgroup + // cant do resets on subgroup 0 + if (sgroup == 0) + { + return; + } + + // this will get the index of the first spawn entry in the subgroup + // it will have the subgroup timer settings + int spawnindex = GetCurrentSequentialSpawnIndex(sgroup); + + if (spawnindex >= 0) + { + // if it is greater than zero then initiate reset + SpawnObject s = m_SpawnObjects[spawnindex]; + NextSeqReset = TimeSpan.FromMinutes(s.SequentialResetTime); + } + } + + + public void ResetSequential() + { + // go back to the lowest level + if (m_SequentialSpawning >= 0) + { + m_SequentialSpawning = NextSequentialIndex(-1); + } + + // reset the nextspawn times + ResetNextSpawnTimes(); + + // and reset the kill count + KillCount = 0; + } + + public bool AdvanceSequential() + { + // check for a sequence hold + + if (HoldSequence) + { + return false; + } + + // check for triggering + if (!((m_proximityActivated || CanFreeSpawn) && TODInRange)) + { + return false; + } + + // if kills needed is greater than zero then check the killcount as well + int spawnindex = GetCurrentSequentialSpawnIndex(m_SequentialSpawning); + + int killsneeded = 0; + int subgroup = -1; + bool clearedobjects = false; + + if (spawnindex >= 0) + { + SpawnObject s = m_SpawnObjects[spawnindex]; + subgroup = s.SubGroup; + killsneeded = s.KillsNeeded; + } + + // advance the sequential spawn index if it is enabled and kills needed have been satisfied + if (m_SequentialSpawning >= 0 && (killsneeded == 0 || KillCount >= killsneeded)) + { + m_SequentialSpawning = NextSequentialIndex(m_SequentialSpawning); + + // set the sequential reset based on the current sequence state + // this will be checked in the spawner OnTick to determine whether to Reset the sequential state + InitiateSequentialReset(m_SequentialSpawning); + + // clear the spawns if there is a killcount on the level + if (killsneeded >= 0) + { + ClearSubgroup(subgroup); + clearedobjects = true; + } + + // and reset the kill count + KillCount = 0; + } + + // returning true will indicate that all spawns have been cleared and therefore a new spawn can be initiated in the same OnTick + return clearedobjects; + } + + int killcount_held; + + public void OnTick() + { + _TraceStart(8); + // start up the timer again for the next Ontick + DoTimer(); + + // reset the protection against runaway looping + ClearSpawnedThisTick = true; + + // if regional spawning is enabled, update the region in case new regions were added after the initialization pass + CheckRegionAssignment = true; + + // reset the killcount whenever a spawntick goes by in which it could have spawned, ie the spawner is full, or proximity triggered + // spawns were not activated. Note that killcount gets incremented within Defrag whenever a spawn is that had been generated is removed from the active list. + // Check the count before and then after the spawn passes. + // if the spawner is still refractory then dont do a reset of the killcount. + //int startcount = this.m_killcount; + int startcount = killcount_held; + if (!m_skipped) + { + killcount_held = m_killcount; + } + + // killcount will be updated in Defrag + Defrag(true); + + // remove any keyword tags that were made + // note, tags only last a single ontick except for WAIT type + ClearTags(false); + + if (!m_DisableGlobalAutoReset && startcount == m_killcount && !m_refractActivated && !m_skipped) + { + m_spawncheck--; + } + m_skipped = false; + + // allow for some slack in the killcount reset by resetting after a certain number of spawn ticks without kills pass + if (m_spawncheck <= 0) + { + m_killcount = 0; + m_spawncheck = m_KillReset; // wait for 1 spawn ticks to pass before resetting. This can be set to anything you like + } + + // check for smart spawning + if (SmartSpawning && IsFull && !HasActiveSectors && !HasDamagedOrDistantSpawns /*&& !HasHoldSmartSpawning */) + { + IsInactivated = true; + // for multiple sector spawning ranges use the sector timer, otherwise just rely on OnSectorActivate to detect sector activation + //if (!UseSectorActivate) + //DoSectorTimer(TimeSpan.FromSeconds(1)); + + SmartRemoveSpawnObjects(); + + } + + // dont process spawn ticks while inactivated if smart spawning is enabled + if (SmartSpawning && IsInactivated) + { + _TraceEnd(8); + return; + } + + IsInactivated = false; + + // check to see if spawning is on hold due to a WAIT keyword + if (!OnHold) + { + // look for triggers that are not player activated. + if (m_ProximityRange == -1 && CanSpawn) + { + CheckTriggers(null, null, false); + } + + // check for proximity triggers without movement activation + if (m_ProximityRange >= 0 && CanSpawn) + { + // check all nearby players + IPooledEnumerable eable = GetMobilesInRange(m_ProximityRange); + foreach (Mobile p in eable) + { + if (ValidPlayerTrig(p)) + { + CheckTriggers(p, null, true); + } + } + + eable.Free(); + } + + if (m_Group) + { + // check the seq reset time on the current subgroup + // if the reset time is greater than zero then check the timer + // if it has expired then reset the sequential subgroup + // only do this if it can actually spawn + if (CheckForSequentialReset()) + { + // it has expired so reset the sequential spawn level + SeqResetTo(m_SequentialSpawning); + + bool triedtospawn = TryRespawn(); + + if (triedtospawn) + { + ClearGOTOTags(); + } + + // dont advance if the spawn isnt triggered after resetting + if (!triedtospawn) + { + HoldSequence = true; + } + } + else if (TotalSpawnedObjects <= 0) + { + + // advance the sequential spawn index if it is enabled + AdvanceSequential(); + + //bool hadhold = HoldSequence; + + //HoldSequence = false; + + bool triedtospawn = TryRespawn(); + + if (triedtospawn) + { + ClearGOTOTags(); + } + + //if (!triedtospawn) HoldSequence = hadhold; + } + } + else + { + + if (CheckForSequentialReset()) + { + // it has expired so reset the sequential spawn level + SeqResetTo(m_SequentialSpawning); + + // dont advance if the spawn isnt triggered after resetting + HoldSequence = true; + } + else + { + // advance the sequence before spawning + AdvanceSequential(); + } + + // keep track of the hold flag before trying to spawn in case no spawn attempt is made + //bool hadhold = HoldSequence; + + // clear the hold flag to see if any of the spawned entries try to set it + //HoldSequence = false; + + // try to spawn. If spawning conditions such as triggering or TOD are not met, then it returns false + bool triedtospawn = Spawn(false, 0); + + if (triedtospawn) + { + ClearGOTOTags(); + } + // this will maintain any sequential holds if spawning was suppressed due to triggering + + if (!FreeRun) + { + m_mob_who_triggered = null; + } + + } + + // remove any keyword tags that were made except for WAIT type + ClearTags(false); + + + // and clear triggering flags + if (!OnHold && !FreeRun) + { + m_proximityActivated = false; + } + } + + if (FreeRun && SpawnOnTrigger && m_proximityActivated) + { + // if it is in free run and was triggered, then just keep spawning as though it was triggered immediately + NextSpawn = TimeSpan.Zero; + ResetNextSpawnTimes(); + } + + + //this.m_ExternalTrigger = false; + // if it is out of the TOD range then delete the spawns + if (!TODInRange) + { + RemoveSpawnObjects(); + + ResetAllFlags(); + } + _TraceEnd(8); + + } + + public bool ClearSpawnedThisTick + { + set + { + if (m_SpawnObjects == null || value == false) + { + return; + } + + for (int i = 0; i < m_SpawnObjects.Count; i++) + { + SpawnObject sobj = m_SpawnObjects[i]; + if (sobj != null) + { + sobj.SpawnedThisTick = false; + } + } + } + } + + // select and spawn something + // return false if it cannot spawn, e.g. there is nothing to spawn or it is a triggerable spawner and has not been triggered + public bool Spawn(bool smartspawn, byte loops) + { + if (m_SpawnObjects != null && m_SpawnObjects.Count > 0 && (m_proximityActivated || CanFreeSpawn) && TODInRange) + { + m_HoldSequence = false; + + // if the spawner is full then dont bother + if (IsFull) + { + ResetProximityActivated(); + return true; + } + + // Pick a spawn object to spawn + int SpawnIndex; + + // see if sequential spawning has been selected + SpawnIndex = m_SequentialSpawning >= 0 ? GetCurrentAvailableSequentialSpawnIndex(m_SequentialSpawning) : RandomAvailableSpawnIndex(); + + // no spawns are available so no point in continuing + if (SpawnIndex < 0) + { + ResetProximityActivated(); + return true; + } + + SpawnObject sobj = m_SpawnObjects[SpawnIndex]; + int sgroup = sobj.SubGroup; + + // if this is part of a non-zero group, then spawn all of the group members as well + if (sgroup != 0) + { + SpawnSubGroup(sgroup, smartspawn, loops); + } + else + { + // Found a valid spawn object so spawn it and see if it successful + if (Spawn(SpawnIndex, smartspawn, sobj.SpawnsPerTick, loops)) + { + if (!smartspawn) + { + RefreshNextSpawnTime(sobj); + } + } + } + + ResetProximityActivated(); + return true; + } + + ResetProximityActivated(); + return false; + } + + // spawn an individual entry by index up to count times + public bool Spawn(int index, bool smartspawn, int count, int packrange, Point3D packcoord, bool ignoreloopprotection, byte loops) + { + if (m_SpawnObjects == null || index >= m_SpawnObjects.Count) + { + return false; + } + + bool didspawn = false; + + SpawnObject so = m_SpawnObjects[index]; + + if (so == null) + { + return false; + } + + Defrag(false); + + // make sure you dont go over the individual entry maxcount + int somax = so.MaxCount; + int socnt = so.SpawnedObjects.Count; + int nspawn = so.SpawnsPerTick; + int scnt = SafeCurrentCount; + + for (int k = 0; k < nspawn && k + socnt < somax && k + scnt < MaxCount; k++) + { + if (packrange >= 0 && so.SubGroup > 0 && packcoord == Point3D.Zero) + { + packcoord = GetPackCoord(so.SubGroup); + } + if (Spawn(index, smartspawn, packrange, packcoord, ignoreloopprotection, loops)) + { + // if any of the attempts were successful then flag it as having spawned + didspawn = true; + } + } + + return didspawn; + } + + // spawn an individual entry by index up to count times + public bool Spawn(int index, bool smartspawn, int count, byte loops) => Spawn(index, smartspawn, count, false, loops); + + // spawn an individual entry by index up to count times + public bool Spawn(int index, bool smartspawn, int count, bool ignoreloopprotection, byte loops) => Spawn(index, smartspawn, count, -1, Point3D.Zero, ignoreloopprotection, loops); + + // spawn an individual entry by spawn object + public void Spawn(string SpawnObjectTypeName, bool smartspawn, int packrange, Point3D packcoord, byte loops) + { + if (m_SpawnObjects == null) + { + return; + } + + for (int i = 0; i < m_SpawnObjects.Count; i++) + { + if (m_SpawnObjects[i].TypeName.ToUpper() == SpawnObjectTypeName.ToUpper()) + { + + if (Spawn(i, smartspawn, packrange, packcoord, loops)) + { + RefreshNextSpawnTime(m_SpawnObjects[i]); + } + break; + } + } + } + + // spawn an individual entry by index + public void Spawn(string SpawnObjectTypeName, bool smartspawn, byte loops) + { + Spawn(SpawnObjectTypeName, smartspawn, -1, Point3D.Zero, loops); + } + + // spawn an individual entry by index + public bool Spawn(int index, bool smartspawn, int packrange, Point3D packcoord, byte loops) => Spawn(index, smartspawn, packrange, packcoord, false, loops); + + // spawn an individual entry by index + public bool Spawn(int index, bool smartspawn, int packrange, Point3D packcoord, bool ignoreloopprotection, byte loops) + { + Map map = Map; + + // Make sure everything is ok to spawn an object + if (map == null || + map == Map.Internal || + m_SpawnObjects == null || + m_SpawnObjects.Count == 0 || + index < 0 || + index >= m_SpawnObjects.Count + ) + { + return false; + } + + // Remove any spawns that don't belong to the spawner any more. + Defrag(false); + + // Get the spawn object at the required index + SpawnObject TheSpawn = m_SpawnObjects[index]; + + // Check if the object retrieved is a valid SpawnObject + if (TheSpawn != null) + { + // dont allow an entry to be spawned more than once per tick + // this protects against runaway recursive looping + if (TheSpawn.SpawnedThisTick && !ignoreloopprotection) + { + return false; + } + + // check the nextspawn time to see if it is available + if (TheSpawn.NextSpawn > DateTime.UtcNow) + { + return false; + } + + int CurrentCreatureMax = TheSpawn.MaxCount; + int CurrentCreatureCount = TheSpawn.SpawnedObjects.Count; + + // Check that the current object to be spawned has not reached its maximum allowed + // and make sure that the maximum spawner count has not been exceeded as well + if (CurrentCreatureCount >= CurrentCreatureMax || + TotalSpawnedObjects >= m_Count) + { + return false; + } + + // check for string substitions + string substitutedtypeName = BaseXmlSpawner.ApplySubstitution(this, this, TheSpawn.TypeName); + + // random positioning is the default + List spawnpositioning = null; + + // require valid surfaces by default + bool requiresurface = true; + + // parse the # function specification for the entry + while (substitutedtypeName.StartsWith("#")) + { + string[] args = BaseXmlSpawner.ParseSemicolonArgs(substitutedtypeName, 2); + + if (args.Length > 0) + { + if (spawnpositioning == null) + { + spawnpositioning = new List(); + } + // parse any comma args + string[] keyvalueargs = BaseXmlSpawner.ParseCommaArgs(args[0], 10); + + if (keyvalueargs.Length > 0) + { + + switch (keyvalueargs[0]) + { + case "#NOITEMID": + { + spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.NoItemID, m_mob_who_triggered, keyvalueargs)); + break; + } + case "#ITEMID": + { + spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.ItemID, m_mob_who_triggered, keyvalueargs)); + break; + } + case "#NOTILES": + { + spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.NoTiles, m_mob_who_triggered, keyvalueargs)); + break; + } + case "#TILES": + { + spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.Tiles, m_mob_who_triggered, keyvalueargs)); + break; + } + case "#WET": + { + spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.Wet, m_mob_who_triggered, keyvalueargs)); + break; + } + case "#XFILL": + { + spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.RowFill, m_mob_who_triggered, keyvalueargs)); + break; + } + case "#YFILL": + { + spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.ColFill, m_mob_who_triggered, keyvalueargs)); + break; + } + case "#EDGE": + { + spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.Perimeter, m_mob_who_triggered, keyvalueargs)); + break; + } + case "#PLAYER": + { + spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.Player, m_mob_who_triggered, keyvalueargs)); + break; + } + case "#WAYPOINT": + { + spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.Waypoint, m_mob_who_triggered, keyvalueargs)); + break; + } + case "#RELXY": + { + spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.RelXY, m_mob_who_triggered, keyvalueargs)); + break; + } + case "#DXY": + { + spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.DeltaLocation, m_mob_who_triggered, keyvalueargs)); + break; + } + case "#XY": + { + spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.Location, m_mob_who_triggered, keyvalueargs)); + break; + } + case "#CONDITION": + { + // test the specified condition string + // syntax is #CONDITION,proptest + // reparse with only one arg after the comma, this allows property tests that use commas as well + string[] ckeyvalueargs = BaseXmlSpawner.ParseCommaArgs(args[0], 2); + if (ckeyvalueargs.Length > 1) + { + // dont spawn if it fails the test + if (!BaseXmlSpawner.CheckPropertyString(this, this, ckeyvalueargs[1], out status_str)) + { + return false; + } + } + else + { + status_str = $"invalid #CONDITION specification: {args[0]}"; + } + break; + } + default: + { + status_str = $"invalid # specification: {args[0]}"; + break; + } + } + } + } + + // get the rest of the spawn entry + substitutedtypeName = args.Length > 1 ? args[1].Trim() : string.Empty; + } + + + if (substitutedtypeName.StartsWith("*")) + { + requiresurface = false; + substitutedtypeName = substitutedtypeName.TrimStart('*'); + } + + TheSpawn.RequireSurface = requiresurface; + + string typeName = BaseXmlSpawner.ParseObjectType(substitutedtypeName); + + if (BaseXmlSpawner.IsTypeOrItemKeyword(typeName)) + { + string status_str = null; + + bool completedtypespawn = BaseXmlSpawner.SpawnTypeKeyword(this, TheSpawn, typeName, substitutedtypeName, + m_mob_who_triggered, Map, out status_str, loops); + + if (status_str != null) + { + this.status_str = status_str; + } + + if (completedtypespawn) + { + // successfully spawned the keyword + // note that returning true means that Spawn will assume that it worked and will not try to respawn something else + // added the duration timer that begins on spawning + DoTimer2(m_Duration); + + InvalidateProperties(); + + return true; + } + + return false; + } + + // its a regular type descriptor so find out what it is + Type type = AssemblyHandler.FindTypeByName(typeName); + + // dont try to spawn invalid types, or Mobile type spawns in containers + if (type != null && !(Parent != null && (type == typeof(Mobile) || type.IsSubclassOf(typeof(Mobile))))) + { + + string[] arglist = BaseXmlSpawner.ParseString(substitutedtypeName, 3, "/"); + + object o = CreateObject(type, arglist[0]); + + if (o == null) + { + status_str = $"invalid type specification: {arglist[0]}"; + return true; + } + try + { + if (o is Mobile mob) + { + // if this is in any container such as a pack the xyz values are invalid as map coords so dont spawn the mob + if (Parent is Container) + { + mob.Delete(); + + return true; + } + + // add the mobile to the spawned list + TheSpawn.SpawnedObjects.Add(mob); + + mob.Spawner = this; + + var loc = GetSpawnPosition(requiresurface, packrange, packcoord, spawnpositioning, mob); + + if (!smartspawn) + { + mob.OnBeforeSpawn(loc, map); + } + + mob.MoveToWorld(loc, map); + + if (mob is BaseCreature mobile) + { + mobile.RangeHome = m_HomeRange; + mobile.CurrentWayPoint = m_WayPoint; + + if (m_Team > 0) + { + mobile.Team = m_Team; + } + + // Check if this spawner uses absolute (from spawnER location) + // or relative (from spawnED location) as the mobiles home point + mobile.Home = m_HomeRangeIsRelative ? mobile.Location : Location; + } + + // if the object has an OnSpawned method, then invoke it + if (!smartspawn) + { + mob.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 + string status_str; + + BaseXmlSpawner.ApplyObjectStringProperties(this, substitutedtypeName, mob, m_mob_who_triggered, this, out status_str); + + if (status_str != null) + { + this.status_str = status_str; + } + + InvalidateProperties(); + + // added the duration timer that begins on spawning + DoTimer2(m_Duration); + + return true; + } + + if (o is Item item) + { + string status_str; + + BaseXmlSpawner.AddSpawnItem(this, TheSpawn, item, Location, map, m_mob_who_triggered, requiresurface, spawnpositioning, substitutedtypeName, smartspawn, out status_str); + + if (status_str != null) + { + this.status_str = status_str; + } + + InvalidateProperties(); + + // added the duration timer that begins on spawning + DoTimer2(m_Duration); + + return true; + } + } + catch (Exception ex) { Console.WriteLine("When spawning {0}, {1}", o, ex); } + } + else + { + status_str = $"invalid type specification: {typeName}"; + return true; + } + } + return false; + } + + public bool SpawnSubGroup(int sgroup, byte loops) => SpawnSubGroup(sgroup, false, loops); + + public bool SpawnSubGroup(int sgroup, bool smartspawn, byte loops) => SpawnSubGroup(sgroup, false, false, loops); + + public bool SpawnSubGroup(int sgroup, bool smartspawn, bool ignoreloopprotection, byte loops) + { + if (m_SpawnObjects == null) + { + return false; + } + + if (sgroup >= 0) + { + bool didspawn = false; + Point3D packcoord = Point3D.Zero; + + for (int j = 0; j < m_SpawnObjects.Count; j++) + { + SpawnObject so = m_SpawnObjects[j]; + + if (so != null && so.SubGroup == sgroup) + { + // find the first subgroup spawn to determine the packspawning reference coordinates + if (so.PackRange >= 0 && packcoord == Point3D.Zero) + { + packcoord = GetPackCoord(sgroup); + } + + // get the SpawnsPerTick count and spawn up to that number + bool success = Spawn(j, smartspawn, so.SpawnsPerTick, so.PackRange, packcoord, ignoreloopprotection, loops); + + if (success) + { + didspawn = true; + } + + if (success && !smartspawn) + { + RefreshNextSpawnTime(so); + } + } + } + + // success if any of the subgroup spawned + if (didspawn) + { + return true; + } + } + return false; + } + + public Point3D GetPackCoord(int sgroup) + { + for (int j = 0; j < m_SpawnObjects.Count; j++) + { + SpawnObject so = m_SpawnObjects[j]; + + if (so != null && so.SubGroup == sgroup && so.SpawnedObjects.Count > 0 && so.PackRange >= 0) + { + // if pack spawning is enabled for this subgroup, then get the + // the origin for pack spawning using the first existing pack spawn + // in the subgroup + + for (int i = 0; i < so.SpawnedObjects.Count; ++i) + { + object o = so.SpawnedObjects[i]; + if (o is Item item) + { + return item.Location; + } + + if (o is Mobile mobile) + { + return mobile.Location; + } + } + } + } + + return Point3D.Zero; + } + + + //used by the reset button in the gump + public void ResetAllFlags() + { + m_proximityActivated = false; + m_ExternalTrigger = false; + m_durActivated = false; + m_refractActivated = false; + m_mob_who_triggered = null; + m_killcount = 0; + m_GumpState = null; + FreeRun = false; + } + + public bool BringHome + { + set { if (value) + { + BringToHome(); + } + } + } + + public void BringToHome() + { + if (m_SpawnObjects == null) + { + return; + } + + Defrag(false); + + foreach (SpawnObject so in m_SpawnObjects) + { + for (int i = 0; i < so.SpawnedObjects.Count; ++i) + { + object o = so.SpawnedObjects[i]; + + if (o is Mobile mobile) + { + mobile.Map = Map; + mobile.Location = new Point3D(Location); + } + else if (o is Item item) + { + item.MoveToWorld(Location, Map); + } + } + } + } + + public bool CheckRegionAssignment + { + get => false; + set + { + if (value) + { + // see if a region definition needs updating + if (m_Region == null && m_RegionName != null && RegionName != string.Empty) + { + RegionName = RegionName; + + if (SpawnRegion != null) + { + // clear the status if successful + status_str = null; + } + } + } + } + } + + public void Start() + { + if (m_Running == false) + { + if (m_SpawnObjects != null && m_SpawnObjects.Count > 0) + { + m_Running = true; + DoTimer(); + } + } + } + + public void Stop() + { + if (m_Running) + { + // turn off all timers + if (m_Timer != null) + { + m_Timer.Stop(); + } + + if (m_DurTimer != null) + { + m_DurTimer.Stop(); + } + + if (m_RefractoryTimer != null) + { + m_RefractoryTimer.Stop(); + } + + m_Running = false; + m_proximityActivated = false; + m_ExternalTrigger = false; + m_mob_who_triggered = null; + } + } + + public void Reset() + { + Stop(); + // reset the protection against runaway looping + ClearSpawnedThisTick = true; + RemoveSpawnObjects(); + ClearTags(true); + ResetAllFlags(); + status_str = ""; + m_killcount = 0; + OnHold = false; + mostRecentSpawnPosition = Point3D.Zero; + spawnPositionWayTable = null; + // dont advance before the next spawn + HoldSequence = true; + IsInactivated = false; + ResetSequential(); + } + + public void Respawn() + { + TryRespawn(); + } + + public bool TryRespawn() + { + inrespawn = true; + IsInactivated = false; + + // reset the protection against runaway looping + ClearSpawnedThisTick = true; + + // Delete all currently spawned objects + RemoveSpawnObjects(); + + // added the explicit start. Previously it relied on the automatic start that occurred when the spawnobject list was updated. + Start(); + + ResetNextSpawnTimes(); + + // Respawn all objects up to the spawners current maximum allowed + // note that by default, for proximity sensing, the spawner will only trigger once, but for respawns allow them all + bool keepProximityActivated = m_proximityActivated; + + bool triedtospawn = false; + + // attempt to spawn up to the MaxCount of the spawner + for (int x = 0; x < m_Count; x++) + { + triedtospawn = Spawn(false, 0); + + if (x < m_Count - 1 || OnHold) + { + m_proximityActivated = keepProximityActivated; + } + } + if (!FreeRun) + { + m_mob_who_triggered = null; + } + + ClearTags(true); + + inrespawn = false; + + return triedtospawn; + } + + // used to optimize smartspawning use of hasholdsmartspawning + public void SmartRespawn() + { + inrespawn = true; + IsInactivated = false; + + // reset the protection against runaway looping + ClearSpawnedThisTick = true; + + // Delete all currently spawned objects + SmartRemoveSpawnObjects(); + + // added the explicit start. Previously it relied on the automatic start that occurred when the spawnobject list was updated. + Start(); + + ResetNextSpawnTimes(); + + // Respawn all objects up to the spawners current maximum allowed + // note that by default, for proximity sensing, the spawner will only trigger once, but for respawns allow them all + bool keepProximityActivated = m_proximityActivated; + + // attempt to spawn up to the MaxCount of the spawner + for (int x = 0; x < m_Count; x++) + { + Spawn(true, 0); + + if (x < m_Count - 1 || OnHold) + { + m_proximityActivated = keepProximityActivated; + } + } + + if (!FreeRun) + { + m_mob_who_triggered = null; + } + + ClearTags(true); + + inrespawn = false; + } + + + public void SortSpawns() + { + if (m_SpawnObjects == null) + { + return; + } + + // establish the entry order + int count = 0; + + foreach (SpawnObject so in m_SpawnObjects) + { + so.EntryOrder = count++; + } + + m_SpawnObjects.Sort(new SubgroupSorter()); + } + + private class SubgroupSorter : IComparer + { + public int Compare(SpawnObject a, SpawnObject b) + { + if (a.SubGroup == b.SubGroup) + { + // use the entry order as the secondary sort factor + return a.EntryOrder - b.EntryOrder; + } + + return a.SubGroup - b.SubGroup; + } + } + + public static SpawnObject GetSpawnObject(XmlSpawner spawner, int sgroup) + { + if (spawner == null || spawner.m_SpawnObjects == null) + { + return null; + } + + for (int i = 0; i < spawner.m_SpawnObjects.Count; i++) + { + // find the first entry with matching subgroup id + if (spawner.m_SpawnObjects[i].SubGroup == sgroup) + { + return spawner.m_SpawnObjects[i]; + } + } + return null; + } + + public static object GetSpawned(XmlSpawner spawner, int sgroup) + { + if (spawner == null || spawner.m_SpawnObjects == null) + { + return null; + } + + for (int i = 0; i < spawner.m_SpawnObjects.Count; i++) + { + // find the first entry with matching subgroup id + if (spawner.m_SpawnObjects[i].SubGroup == sgroup) + { + // find the first spawned object in the entry + if (spawner.m_SpawnObjects[i].SpawnedObjects.Count > 0) + { + return spawner.m_SpawnObjects[i].SpawnedObjects[0]; + } + } + } + return null; + } + + public static List GetSpawnedList(XmlSpawner spawner, int sgroup) + { + List newlist = new List(); + + if (spawner == null || spawner.m_SpawnObjects == null) + { + return null; + } + + for (int i = 0; i < spawner.m_SpawnObjects.Count; i++) + { + // find the first entry with matching subgroup id + if (spawner.m_SpawnObjects[i].SubGroup == sgroup) + { + // find the first spawned object in the entry + + if (spawner.m_SpawnObjects[i].SpawnedObjects.Count > 0) + { + for (int j = 0; j < spawner.m_SpawnObjects[i].SpawnedObjects.Count; j++) + { + newlist.Add(spawner.m_SpawnObjects[i].SpawnedObjects[j]); + } + } + } + } + return newlist; + } + + public bool HasSubGroups() + { + if (m_SpawnObjects == null) + { + return false; + } + + for (int j = 0; j < m_SpawnObjects.Count; j++) + { + if (m_SpawnObjects[j].SubGroup > 0) + { + return true; + } + } + + return false; + } + + private void ResetProximityActivated() + { + // dont reset triggering if free run mode has been selected + if (!FreeRun) + { + m_proximityActivated = false; + } + } + + public bool HasIndividualSpawnTimes() + { + + if (m_SpawnObjects != null && m_SpawnObjects.Count > 0) + { + for (int i = 0; i < m_SpawnObjects.Count; i++) + { + SpawnObject so = m_SpawnObjects[i]; + + if (so.MinDelay != -1 || so.MaxDelay != -1) + { + return true; + } + } + } + return false; + } + + private void ResetNextSpawnTimes() + { + + if (m_SpawnObjects != null && m_SpawnObjects.Count > 0) + { + for (int i = 0; i < m_SpawnObjects.Count; i++) + { + SpawnObject so = m_SpawnObjects[i]; + + so.NextSpawn = DateTime.UtcNow; + } + } + } + + public void RefreshNextSpawnTime(SpawnObject so) + { + if (so == null) + { + return; + } + + int mind = (int)(so.MinDelay * 60); + int maxd = (int)(so.MaxDelay * 60); + if (mind < 0 || maxd < 0) + { + so.NextSpawn = DateTime.UtcNow; + } + else + { + + TimeSpan delay = TimeSpan.FromSeconds(Utility.RandomMinMax(mind, maxd)); + + so.NextSpawn = DateTime.UtcNow + delay; + } + + } + + public static bool IsValidMapLocation(int X, int Y, Map map) + { + if (map == null || map == Map.Internal) + { + return false; + } + + // check the location relative to the current map to make sure it is valid + if (X < 0 || X > map.Width || Y < 0 || Y > map.Height) + { + return false; + } + return true; + } + public static bool IsValidMapLocation(Point3D location, Map map) + { + if (map == null || map == Map.Internal) + { + return false; + } + + // check the location relative to the current map to make sure it is valid + if (location.X < 0 || location.X > map.Width || location.Y < 0 || location.Y > map.Height) + { + return false; + } + return true; + } + public static bool IsValidMapLocation(Point2D location, Map map) + { + if (map == null || map == Map.Internal) + { + return false; + } + + // check the location relative to the current map to make sure it is valid + if (location.X < 0 || location.X > map.Width || location.Y < 0 || location.Y > map.Height) + { + return false; + } + return true; + } + + private static WayPoint GetWaypoint(string waypointstr) + { + WayPoint waypoint = null; + + // try parsing the waypoint name to determine the waypoint. object syntax is "SERIAL,sernumber" or "waypointname" + if (!string.IsNullOrEmpty(waypointstr)) + { + string[] wayargs = BaseXmlSpawner.ParseString(waypointstr, 2, ","); + if (wayargs != null && wayargs.Length > 0) + { + // is this a SERIAL specification? + if (wayargs[0] == "SERIAL") + { + + // look it up by serial + if (wayargs.Length > 1) + { + uint sernum; + try + { + sernum = (uint)Convert.ToUInt64(wayargs[1][2..], 16); + IEntity e = World.FindEntity((Serial)sernum); + + if (e is WayPoint point) + { + waypoint = point; + } + } + catch { } + } + } + else + { + // just look it up by name + Item wayitem = BaseXmlSpawner.FindItemByName(null, wayargs[0], "WayPoint"); + if (wayitem is WayPoint point) + { + waypoint = point; + } + } + } + } + + return waypoint; + } + + private static bool HasTileSurface(Map map, int X, int Y, int Z) + { + if (map == null) + { + return false; + } + + StaticTile[] tiles = map.Tiles.GetStaticTiles(X, Y, true); + + if (tiles == null) + { + return false; + } + + // go through the tiles and see if any are at the Z location + foreach (StaticTile o in tiles) + { + StaticTile i = o; + + if (i.Z + i.Height == Z) + { + return true; + } + } + + return false; + } + + private bool CheckHoldSmartSpawning(object o) + { + if (o == null) + { + return false; + } + + // try looking this up in the lookup table + if (holdSmartSpawningHash == null) + { + holdSmartSpawningHash = new Dictionary(); + } + PropertyInfo prop; + if (!holdSmartSpawningHash.TryGetValue(o.GetType(), out prop)) + { + prop = o.GetType().GetProperty("HoldSmartSpawning"); + // check to make sure the HoldSmartSpawning property for this object has the right type + if (prop != null && (!prop.CanRead || prop.PropertyType != typeof(bool))) + { + prop = null; + } + + holdSmartSpawningHash[o.GetType()] = prop; + } + + if (prop != null) + { + try + { + return (bool)prop.GetValue(o, null); + } + catch { } + } + + return false; + } + + public bool HasHoldSmartSpawning + { + get + { + // go through the spawn lists + foreach (SpawnObject so in m_SpawnObjects) + { + for (int x = 0; x < so.SpawnedObjects.Count; x++) + { + object o = so.SpawnedObjects[x]; + if (CheckHoldSmartSpawning(o)) + { + return true; + } + } + } + + return false; + } + } + + // if a non-null mob argument is passed, then check the canswim and cantwalk props to determine valid placement + public bool CanFit(int x, int y, int z, int height, bool checkBlocksFit, bool checkMobiles, bool requireSurface, Mobile mob) + { + Map map = Map; + + if (DebugThis) + { + Console.WriteLine("CanFit mob {0}, map={1}", mob, map); + } + if (map == null || map == Map.Internal) + { + return false; + } + + if (x < 0 || y < 0 || x >= map.Width || y >= map.Height) + { + return false; + } + + bool hasSurface = false; + bool checkmob = false; + bool canswim = false; + bool cantwalk = false; + + if (mob != null) + { + checkmob = true; + canswim = mob.CanSwim; + cantwalk = mob.CantWalk; + } + if (DebugThis) + { + Console.WriteLine("fitting mob {0} checkmob={1} swim={2} walk={3}", mob, checkmob, canswim, cantwalk); + } + LandTile lt = map.Tiles.GetLandTile(x, y); + + bool surface; + bool wet = false; + + map.GetAverageZ(x, y, out var lowZ, out var avgZ, out var topZ); + TileFlag landFlags = TileData.LandTable[lt.ID & TileData.MaxLandValue].Flags; + + if (DebugThis) + { + Console.WriteLine("landtile at {0},{1},{2} lowZ={3} avgZ={4} topZ={5}", x, y, z, lowZ, avgZ, topZ); + } + + var impassable = (landFlags & TileFlag.Impassable) != 0; + if (checkmob) + { + wet = (landFlags & TileFlag.Wet) != 0; + // dont allow wateronly creatures on land + if (cantwalk && !wet) + { + impassable = true; + } + + // allow water creatures on water + if (canswim && wet) + { + impassable = false; + } + } + + if (impassable && avgZ > z && z + height > lowZ) + { + return false; + } + + if (!impassable && z == avgZ && !lt.Ignored) + { + hasSurface = true; + } + + if (DebugThis) + { + Console.WriteLine("landtile at {0},{1},{2} wet={3} impassable={4} hassurface={5}", x, y, z, wet, impassable, hasSurface); + } + + StaticTile[] staticTiles = map.Tiles.GetStaticTiles(x, y, true); + + for (int i = 0; i < staticTiles.Length; ++i) + { + ItemData id = TileData.ItemTable[staticTiles[i].ID & TileData.MaxItemValue]; + surface = id.Surface; + impassable = id.Impassable; + if (checkmob) + { + wet = (id.Flags & TileFlag.Wet) != 0; + // dont allow wateronly creatures on land + if (cantwalk && !wet) + { + impassable = true; + } + + // allow water creatures on water + if (canswim && wet) + { + surface = true; + impassable = false; + } + } + + if ((surface || impassable) && staticTiles[i].Z + id.CalcHeight > z && z + height > staticTiles[i].Z) + { + return false; + } + + if (surface && !impassable && z == staticTiles[i].Z + id.CalcHeight) + { + hasSurface = true; + } + } + if (DebugThis) + { + Console.WriteLine("statics hassurface={0}", hasSurface); + } + + Sector sector = map.GetSector(x, y); + List items = sector.Items; + List mobs = sector.Mobiles; + + for (int i = 0; i < items.Count; ++i) + { + Item item = items[i]; + + if (item.ItemID < 0x4000 && item.AtWorldPoint(x, y)) + { + ItemData id = item.ItemData; + surface = id.Surface; + impassable = id.Impassable; + if (checkmob) + { + wet = (id.Flags & TileFlag.Wet) != 0; + // dont allow wateronly creatures on land + if (cantwalk && !wet) + { + impassable = true; + } + + // allow water creatures on water + if (canswim && wet) + { + surface = true; + impassable = false; + } + } + + if ((surface || impassable || checkBlocksFit && item.BlocksFit) && item.Z + id.CalcHeight > z && z + height > item.Z) + { + return false; + } + + if (surface && !impassable && !item.Movable && z == item.Z + id.CalcHeight) + { + hasSurface = true; + } + } + } + + if (DebugThis) + { + Console.WriteLine("items hassurface={0}", hasSurface); + } + + if (checkMobiles) + { + for (int i = 0; i < mobs.Count; ++i) + { + Mobile m = mobs[i]; + + if (m.Location.X == x && m.Location.Y == y && (m.AccessLevel == AccessLevel.Player || !m.Hidden)) + { + if (m.Z + 16 > z && z + height > m.Z) + { + return false; + } + } + } + } + + if (DebugThis) + { + Console.WriteLine("return requiresurface={0} hassurface={1}", requireSurface, hasSurface); + } + + return !requireSurface || hasSurface; + } + + public bool CanSpawnMobile(int x, int y, int z, Mobile mob) + { + if (DebugThis) + { + Console.WriteLine("CanSpawnMobile mob {0}", mob); + } + + return Region.Find(new Point3D(x, y, z), Map).AllowSpawn() && Map.CanFit(x, y, z, 16); + } + + public static bool HasRegionPoints(Region r) => r != null && r.Area.Length > 0; + + public Rectangle2D SpawnerBounds => new(m_X, m_Y, m_Width + 1, m_Height + 1); + + private void FindTileLocations(ref List locations, Map map, int startx, int starty, int width, int height, List includetilelist, List excludetilelist, TileFlag tileflag, bool checkitems, int spawnerZ) + { + if (width < 0 || height < 0 || map == null) + { + return; + } + + if (locations == null) + { + locations = new List(); + } + + bool includetile; + bool excludetile; + + for (int x = startx; x <= startx + width; x++) + { + for (int y = starty; y <= starty + height; y++) + { + bool allok = false; + Point3D p = Point3D.Zero; + // go through all of the tiles at the location and find those that are in the allowed tiles list + LandTile ltile = map.Tiles.GetLandTile(x, y); + TileFlag lflags = TileData.LandTable[ltile.ID & TileData.MaxLandValue].Flags; + + // check the land tile + if (includetilelist != null && includetilelist.Count > 0) + { + includetile = includetilelist.Contains(ltile.ID & TileData.MaxLandValue); + } + else + { + includetile = true; + } + + // non-excluded tiles must also be passable + if (excludetilelist != null && excludetilelist.Count > 0) + { + // also require the tile to be passable + excludetile = (lflags & TileFlag.Impassable) != 0 || excludetilelist.Contains(ltile.ID & TileData.MaxLandValue); + } + else + { + excludetile = false; + } + + if (includetile && !excludetile && (lflags & tileflag) == tileflag) + { + p = new Point3D(x, y, ltile.Z + ltile.Height); + allok = true; + } + + StaticTile[] statictiles = map.Tiles.GetStaticTiles(x, y, true); + + // check the static tiles + for (int i = 0; i < statictiles.Length; ++i) + { + StaticTile stile = statictiles[i]; + TileFlag sflags = TileData.ItemTable[stile.ID & TileData.MaxItemValue].Flags; + + if (includetilelist != null && includetilelist.Count > 0) + { + includetile = includetilelist.Contains(stile.ID & TileData.MaxItemValue); + } + else + { + includetile = true; + } + + // non-excluded tiles must also be passable + if (excludetilelist != null && excludetilelist.Count > 0) + { + excludetile = (sflags & TileFlag.Impassable) != 0 || excludetilelist.Contains(stile.ID & TileData.MaxItemValue); + } + else + { + excludetile = false; + } + + if (includetile && !excludetile && (sflags & tileflag) == tileflag) + { + //Console.WriteLine("found statictile {0}/{1} at {2},{3},{4}", stile.ID, stile.ID & 0x3fff, x, y, stile.Z + stile.Height); + if (p == Point3D.Zero) + { + p = new Point3D(x, y, stile.Z + stile.Height); + } + else if (!allok && p.Z - spawnerZ > Math.Abs(stile.Z - spawnerZ)) + { + p = new Point3D(x, y, stile.Z + stile.Height); + } + else if (Math.Abs(ltile.Z - spawnerZ) > Math.Abs(stile.Z - spawnerZ)) //maggiore distanza rispetto allo statico dallo spawner + { + p = new Point3D(x, y, stile.Z + stile.Height); + } + + allok = true; + //locations.Add(new Point3D(x, y, stile.Z + stile.Height)); + //break; + } + } + + if (checkitems) + { + IPooledEnumerable itemslist = map.GetItemsInRange(new Point3D(x, y, 0), 0); + + // check the itemsid + foreach (Item i in itemslist) + { + if (i.ItemData.Impassable) + { + excludetile = true; + } + + TileFlag iflags = TileData.ItemTable[i.ItemID & TileData.MaxItemValue].Flags; + if (includetilelist != null && includetilelist.Count > 0) + { + includetile = includetilelist.Contains(i.ItemID & TileData.MaxItemValue); + } + else + { + includetile = true; + } + + if (excludetilelist != null && excludetilelist.Count > 0) + { + excludetile = excludetilelist.Contains(i.ItemID & TileData.MaxItemValue); + } + else + { + excludetile = false; + } + + if (includetile && !excludetile && (iflags & tileflag) == tileflag) + { + p = new Point3D(x, y, i.Z + i.ItemData.Height); + allok = true; + } + } + + itemslist.Free(); + } + + if (allok && !excludetile) + { + locations.Add(p); + } + } + } + } + + private void FindRegionTileLocations(ref List locations, Region r, List includetilelist, List excludetilelist, TileFlag tileflag, bool checkitems, int spawnerZ) + { + if (r == null || r.Area == null) + { + return; + } + + int count = r.Area.Length; + + if (locations == null) + { + locations = new List(); + } + + // calculate fields of all rectangles (for probability calculating) + for (int n = 0; n < count; n++) + { + Rectangle3D ra = r.Area[n]; + int sx = ra.Start.X; + int sy = ra.Start.Y; + int w = ra.Width; + int h = ra.Height; + + // find all of the valid tile locations in the area + FindTileLocations(ref locations, r.Map, sx, sy, w, h, includetilelist, excludetilelist, tileflag, checkitems, spawnerZ); + } + } + + public Point2D GetRandomRegionPoint(Region r) + { + int count = r.Area.Length; + + int[] FieldArray = new int[count]; + int total = 0; + + // calculate fields of all rectangles (for probability calculating) + for (int i = 0; i < count; i++) + { + Rectangle3D ra = r.Area[i]; + total += FieldArray[i] = ra.Width * ra.Height; + } + + int sum = 0; + int rnd = 0; + if (total > 0) + { + rnd = Utility.Random(total); + } + + int x = 0; + int y = 0; + for (int i = 0; i < count; i++) + { + sum += FieldArray[i]; + if (sum > rnd) + { + Rectangle3D r3d = r.Area[i]; + if (r3d.Width >= 0) + { + x = r3d.Start.X + Utility.Random(r3d.Width); + } + + if (r3d.Height >= 0) + { + y = r3d.Start.Y + Utility.Random(r3d.Height); + } + + break; + } + } + + return new Point2D(x, y); + } + + public Point3D GetSpawnPosition(ISpawnable spawned, Map map) => GetSpawnPosition(true, spawned as Mobile); + + // used for getting non-mobile spawn positions + public Point3D GetSpawnPosition(bool requiresurface) => + // no pack spawning + GetSpawnPosition(requiresurface, -1, Point3D.Zero, null, null); + + // used for getting mobile spawn positions + public Point3D GetSpawnPosition(bool requiresurface, Mobile mob) => + // no pack spawning + GetSpawnPosition(requiresurface, -1, Point3D.Zero, null, mob); + + // used for getting non-mobile spawn positions + public Point3D GetSpawnPosition( + bool requiresurface, + int packrange, + Point3D packcoord, + List spawnpositioning + ) => GetSpawnPosition(requiresurface, packrange, packcoord, spawnpositioning, null); + + public Point3D GetSpawnPosition(bool requiresurface, int packrange, Point3D packcoord, List spawnpositioning, Mobile mob) + { + Map map = Map; + + if (map == null) + { + return Location; + } + + // random positioning by default + SpawnPositionType positioning = SpawnPositionType.Random; + Mobile trigmob = null; + List includetilelist = null; + List excludetilelist = null; + bool checkitems = false; + // restrictions on tile flags + TileFlag tileflag = TileFlag.None; + List locations = null; + + int fillinc = 1; + int positionrange = 0; + string prefix = null; + List WayList = null; + int xinc = 0; + int yinc = 0; + int zinc = 0; + if (spawnpositioning != null) + { + foreach (SpawnPositionInfo s in spawnpositioning) + { + if (s == null) + { + continue; + } + + trigmob = s.trigMob; + string[] positionargs = s.positionArgs; + + // parse the possible args to the spawn position control keywords + switch (s.positionType) + { + case SpawnPositionType.Wet: + { + // syntax Wet + // find all of the wet tiles + tileflag |= TileFlag.Wet; + requiresurface = false; + break; + } + case SpawnPositionType.ItemID: + { + checkitems = true; + goto case SpawnPositionType.Tiles; + } + case SpawnPositionType.NoItemID: + { + checkitems = true; + goto case SpawnPositionType.NoTiles; + } + case SpawnPositionType.Tiles: + { + // syntax Tiles,start[,end] + // get the tiles in the range + requiresurface = false; + int start = -1; + int end = -1; + if (positionargs != null && positionargs.Length > 1) + { + try + { + start = int.Parse(positionargs[1]); + } + catch { } + } + if (positionargs != null && positionargs.Length > 2) + { + try + { + end = int.Parse(positionargs[2]); + } + catch { } + } + if (includetilelist == null) + { + includetilelist = new List(); + } + + // add the tiles to the list + if (start > -1 && end < 0) + { + includetilelist.Add(start); + } + else + if (start > -1 && end > -1) + { + for (int j = start; j <= end; j++) + { + includetilelist.Add(j); + } + } + break; + } + case SpawnPositionType.NoTiles: + { + // syntax Tiles,start[,end] + // get the tiles in the range + requiresurface = false; + int start = -1; + int end = -1; + if (positionargs != null && positionargs.Length > 1) + { + try + { + start = int.Parse(positionargs[1]); + } + catch { } + } + if (positionargs != null && positionargs.Length > 2) + { + try + { + end = int.Parse(positionargs[2]); + } + catch { } + } + if (excludetilelist == null) + { + excludetilelist = new List(); + } + + // add the tiles to the list + if (start > -1 && end < 0) + { + excludetilelist.Add(start); + } + else + if (start > -1 && end > -1) + { + for (int j = start; j <= end; j++) + { + excludetilelist.Add(j); + } + } + break; + } + case SpawnPositionType.RowFill: + case SpawnPositionType.ColFill: + case SpawnPositionType.Perimeter: + { + // syntax XFILL[,inc] + // syntax YFILL[,inc] + // syntax EDGE[,inc] + positioning = s.positionType; + if (positionargs != null && positionargs.Length > 1) + { + try + { + fillinc = int.Parse(positionargs[1]); + } + catch { } + } + break; + } + case SpawnPositionType.RelXY: + case SpawnPositionType.DeltaLocation: + case SpawnPositionType.Location: + { + // syntax RELXY,xinc,yinc[,zinc] + // syntax XY,x,y[,z] + // syntax DXY,dx,dy[,dz] + positioning = s.positionType; + if (positionargs != null && positionargs.Length > 2) + { + try + { + xinc = int.Parse(positionargs[1]); + yinc = int.Parse(positionargs[2]); + } + catch { } + } + if (positionargs != null && positionargs.Length > 3) + { + try + { + zinc = int.Parse(positionargs[3]); + } + catch { } + } + break; + } + case SpawnPositionType.Waypoint: + { + // syntax WAYPOINT,prefix[,range] + positioning = s.positionType; + if (positionargs != null && positionargs.Length > 1) + { + prefix = positionargs[1]; + } + + if (positionargs != null && positionargs.Length > 2) + { + try + { + positionrange = int.Parse(positionargs[2]); + } + catch { } + } + + // find a list of items that match the waypoint prefix + if (prefix != null) + { + // see if there is an existing hashtable for the waypoint lists + if (spawnPositionWayTable == null) + { + spawnPositionWayTable = new Dictionary>(); + } + + // no existing list so create a new one + if (!spawnPositionWayTable.TryGetValue(prefix, out WayList) || WayList == null) + { + WayList = new List(); + + foreach (Item i in World.Items.Values) + { + if (i is WayPoint && !string.IsNullOrEmpty(i.Name) && i.Map == Map && i.Name == prefix) + { + // add it to the list of items + WayList.Add(i); + } + } + // add the new list to the local table + spawnPositionWayTable[prefix] = WayList; + } + } + break; + } + case SpawnPositionType.Player: + { + // syntax PLAYER[,range] + positioning = s.positionType; + if (positionargs != null && positionargs.Length > 1) + { + try + { + positionrange = int.Parse(positionargs[1]); + } + catch { } + } + break; + } + } + } + } + + // precalculate tile locations if they have been specified + if (includetilelist != null || excludetilelist != null || tileflag != TileFlag.None) + { + if (m_Region != null && HasRegionPoints(m_Region)) + { + FindRegionTileLocations(ref locations, m_Region, includetilelist, excludetilelist, tileflag, checkitems, Z); + } + else if (positioning == SpawnPositionType.Random) + { + FindTileLocations(ref locations, Map, m_X, m_Y, m_Width, m_Height, includetilelist, excludetilelist, tileflag, checkitems, Z); + } + } + + // Try 10 times to find a Spawnable location. + // trace profiling indicates that this is a major bottleneck + for (int i = 0; i < 10; i++) + { + int x = X; + int y = Y; + int z = Z; + + int defaultZ = Z; + if (packrange >= 0 && packcoord != Point3D.Zero) + { + defaultZ = packcoord.Z; + } + + if (packrange >= 0 && packcoord != Point3D.Zero) + { + // find a random coord relative to the packcoord + x = packcoord.X - packrange + Utility.Random(packrange * 2 + 1); + y = packcoord.Y - packrange + Utility.Random(packrange * 2 + 1); + } + else if (m_Region != null && HasRegionPoints(m_Region)) + { + // if region spawning is selected then use that to find an x,y loc instead of the spawn box + + if (includetilelist != null || excludetilelist != null || tileflag != TileFlag.None) + { + // use the precalculated tile locations + if (locations != null && locations.Count > 0) + { + Point3D p = locations[Utility.Random(locations.Count)]; + x = p.X; + y = p.Y; + defaultZ = p.Z; + } + } + else + { + Point2D p = GetRandomRegionPoint(m_Region); + x = p.X; + y = p.Y; + } + } + else + { + switch (positioning) + { + case SpawnPositionType.Random: + { + if (includetilelist != null || excludetilelist != null || tileflag != TileFlag.None) + { + + if (locations != null && locations.Count > 0) + { + Point3D p = locations[Utility.Random(locations.Count)]; + x = p.X; + y = p.Y; + defaultZ = p.Z; + } + } + else + { + + if (m_Width > 0) + { + x = m_X + Utility.Random(m_Width + 1); + } + + if (m_Height > 0) + { + y = m_Y + Utility.Random(m_Height + 1); + } + } + break; + } + + case SpawnPositionType.RelXY: + { + x = mostRecentSpawnPosition.X + xinc; + y = mostRecentSpawnPosition.Y + yinc; + defaultZ = mostRecentSpawnPosition.Z + zinc; + break; + } + + case SpawnPositionType.DeltaLocation: + { + x = X + xinc; + y = Y + yinc; + defaultZ = Z + zinc; + break; + } + + case SpawnPositionType.Location: + { + x = xinc; + y = yinc; + defaultZ = zinc; + break; + } + + case SpawnPositionType.RowFill: + { + x = mostRecentSpawnPosition.X + fillinc; + y = mostRecentSpawnPosition.Y; + + if (x < m_X) + { + x = m_X; + } + + if (y < m_Y) + { + y = m_Y; + } + + if (x > m_X + m_Width) + { + x = m_X + (x - m_X - m_Width - 1); + y++; + } + + if (y > m_Y + m_Height) + { + y = m_Y; + } + + break; + } + + case SpawnPositionType.ColFill: + { + x = mostRecentSpawnPosition.X; + y = mostRecentSpawnPosition.Y + fillinc; + + if (x < m_X) + { + x = m_X; + } + + if (y < m_Y) + { + y = m_Y; + } + + if (y > m_Y + m_Height) + { + y = m_Y + (y - m_Y - m_Height - 1); + x++; + } + + if (x > m_X + m_Width) + { + x = m_X; + } + + break; + } + + case SpawnPositionType.Perimeter: + { + x = mostRecentSpawnPosition.X; + y = mostRecentSpawnPosition.Y; + + // if the point is not on the perimeter, reset it to the corner + if (x != m_X && x != m_X + m_Width && y != m_Y && y != m_Y + m_Height) + { + x = m_X; + y = m_Y; + } + + if (y == m_Y && x < m_X + m_Width) + { + x += fillinc; + } + else + if (y == m_Y + m_Height && x > m_X) + { + x -= fillinc; + } + else + if (x == m_X && y > m_Y) + { + y -= fillinc; + } + else + if (x == m_X + m_Width && y < m_Y + m_Height) + { + y += fillinc; + } + + if (x > m_X + m_Width) + { + x = m_X + m_Width; + } + + if (y > m_Y + m_Height) + { + y = m_Y + m_Height; + } + + if (x < m_X) + { + x = m_X; + } + + if (y < m_Y) + { + y = m_Y; + } + + break; + } + + case SpawnPositionType.Player: + { + if (trigmob != null) + { + x = trigmob.Location.X; + y = trigmob.Location.Y; + if (positionrange > 0) + { + x += Utility.Random(positionrange * 2 + 1) - positionrange; + y += Utility.Random(positionrange * 2 + 1) - positionrange; + } + } + break; + } + + case SpawnPositionType.Waypoint: + { + // pick an item randomly from the waylist + if (WayList != null && WayList.Count > 0) + { + int index = Utility.Random(WayList.Count); + Item waypoint = WayList[index]; + if (waypoint != null) + { + x = waypoint.Location.X; + y = waypoint.Location.Y; + defaultZ = waypoint.Location.Z; + if (positionrange > 0) + { + x += Utility.Random(positionrange * 2 + 1) - positionrange; + y += Utility.Random(positionrange * 2 + 1) - positionrange; + } + } + } + + break; + } + } + + mostRecentSpawnPosition = new Point3D(x, y, defaultZ); + } + + // skip invalid points + if (x < 0 || y < 0 || x == 0 && y == 0) + { + continue; + } + + // try to find a valid spawn location using the z coord of the spawner + // relax the normal surface requirement for mobiles if the flag is set + var fit = requiresurface ? CanSpawnMobile(x, y, defaultZ, mob) : Map.CanFit(x, y, defaultZ, SpawnFitSize, true, false, false); + + // if that fails then try to find a valid z coord + if (fit) + { + return new Point3D(x, y, defaultZ); + } + + z = Map.GetAverageZ(x, y); + + fit = requiresurface ? CanSpawnMobile(x, y, z, mob) : Map.CanFit(x, y, z, SpawnFitSize, true, false, false); + + if (fit) + { + return new Point3D(x, y, z); + } + } + + if (packrange >= 0 && packcoord != Point3D.Zero) + { + return packcoord; + } + + return Location; + } + + public int GetCreatureMax(int index) + { + Defrag(false); + + if (m_SpawnObjects == null) + { + return 0; + } + + return m_SpawnObjects[index].MaxCount; + } + + private void DeleteFromList(List list) + { + if (list == null) + { + return; + } + + foreach (object o in list) + { + if (o is Item item) + { + item.Delete(); + } + else if (o is Mobile mobile) + { + mobile.Delete(); + } + } + } + + private void DeleteFromList(List listi, List listm) + { + if (listi != null) + { + int i = listi.Count; + + while (--i >= 0) + { + if (i < listi.Count && listi[i] != null) + { + try + { + listi[i].Delete(); + } + catch + { } + } + } + + listi.Clear(); + } + + if (listm != null) + { + int i = listm.Count; + + while (--i >= 0) + { + if (i < listm.Count && listm[i] != null) + { + try + { + listm[i].Delete(); + } + catch + { } + } + } + + listm.Clear(); + } + } + + public void RemoveSpawnObjects() + { + if (m_SpawnObjects == null) + { + return; + } + + Defrag(false); + + ClearTags(true); + List deletelist = new List(); + foreach (SpawnObject so in m_SpawnObjects) + { + for (int i = 0; i < so.SpawnedObjects.Count; ++i) + { + object o = so.SpawnedObjects[i]; + + if (o is Item || o is Mobile) + { + deletelist.Add(o); + } + } + } + + DeleteFromList(deletelist); + + // Defrag again + Defrag(false); + } + + + public void RemoveSpawnObjects(SpawnObject so) + { + if (so == null) + { + return; + } + + Defrag(false); + + List deletelist = new List(); + + for (int i = 0; i < so.SpawnedObjects.Count; ++i) + { + object o = so.SpawnedObjects[i]; + + if (o is Item || o is Mobile) + { + deletelist.Add(o); + } + } + + DeleteFromList(deletelist); + + // Defrag again + Defrag(false); + } + + public void ClearSubgroup(int subgroup) + { + if (m_SpawnObjects == null) + { + return; + } + + Defrag(false); + + ClearTags(true); + List deletelist = new List(); + foreach (SpawnObject so in m_SpawnObjects) + { + if (so.SubGroup != subgroup || !so.ClearOnAdvance) + { + continue; + } + + for (int i = 0; i < so.SpawnedObjects.Count; ++i) + { + object o = so.SpawnedObjects[i]; + + if (o is Item || o is Mobile) + { + deletelist.Add(o); + } + } + } + + DeleteFromList(deletelist); + + // Defrag again + Defrag(false); + } + + + // used to optimize smart spawning by removing all objects except those that have hold smartspawning + public void SmartRemoveSpawnObjects() + { + if (m_SpawnObjects == null) + { + return; + } + + Defrag(false); + + ClearTags(true); + List deletelist = new List(); + foreach (SpawnObject so in m_SpawnObjects) + { + for (int i = 0; i < so.SpawnedObjects.Count; ++i) + { + object o = so.SpawnedObjects[i]; + + // new optimization for smart spawning to remove all objects except those with hold smartspawning enabled + if (CheckHoldSmartSpawning(o)) + { + continue; + } + + if (o is Item || o is Mobile) + { + deletelist.Add(o); + } + } + } + + DeleteFromList(deletelist); + + // Defrag again + Defrag(false); + } + + public void AddSpawnObject(string SpawnObjectName) + { + if (m_SpawnObjects == null) + { + return; + } + + Defrag(false); + + // Find the spawn object and increment its count by one + foreach (SpawnObject so in m_SpawnObjects) + { + if (so.TypeName.ToUpper() == SpawnObjectName.ToUpper()) + { + // Add one to the total count + m_Count++; + + // Increment the max count for the current creature + so.ActualMaxCount++; + + //only spawn them immediately if the spawner is running + if (Running) + { + Spawn(SpawnObjectName, false, 0); + } + } + } + + InvalidateProperties(); + } + + public void DeleteSpawnObject(Mobile from, string SpawnObjectName) + { + bool WasRunning = m_Running; + + try + { + // Stop spawning for a moment + Stop(); + + // Clean up any spawns marked as deleted + Defrag(false); + + // Keep a reference to the spawn object + SpawnObject TheSpawn = null; + + // Find the spawn object and increment its count by one + foreach (SpawnObject so in m_SpawnObjects) + { + if (so.TypeName.ToUpper() == SpawnObjectName.ToUpper()) + { + // Set the spawn + TheSpawn = so; + break; + } + } + + // Was the spawn object found + if (TheSpawn != null) + { + bool delete_this_entry = false; + + // Decrement the max count for the current creature + TheSpawn.ActualMaxCount--; + + // Make sure the spawn count does not go negative + if (TheSpawn.MaxCount < 0) + { + TheSpawn.MaxCount = 0; + delete_this_entry = true; + } + + if (!delete_this_entry) + { + // Subtract one to the total count + m_Count--; + } + + // Make sure the count does not go negative + if (m_Count < 0) + { + m_Count = 0; + + } + + List deletelist = new List(); + + // Remove any spawns over the count + while (TheSpawn.SpawnedObjects != null && TheSpawn.SpawnedObjects.Count > 0 && TheSpawn.SpawnedObjects.Count > TheSpawn.MaxCount) + { + object o = TheSpawn.SpawnedObjects[0]; + + // Delete the object + if (o is Item || o is Mobile) + { + deletelist.Add(o); + } + + TheSpawn.SpawnedObjects.Remove(o); + } + + DeleteFromList(deletelist); + + // Check if the spawn object should be removed + if (delete_this_entry) + { + m_SpawnObjects.Remove(TheSpawn); + if (from != null) + { + CommandLogging.WriteLine(from, "{0} {1} removed from XmlSpawner {2} '{3}' [{4}, {5}] ({6}) : {7}", from.AccessLevel, CommandLogging.Format(from), Serial, Name, GetWorldLocation().X, GetWorldLocation().Y, Map, SpawnObjectName); + } + } + } + + InvalidateProperties(); + } + finally + { + if (WasRunning) + { + Start(); + } + } + } + + public void RemoveSpawnObject(SpawnObject so) + { + if (m_SpawnObjects.Contains(so)) + { + m_SpawnObjects.Remove(so); + } + } + + public static object CreateObject(Type type, string itemtypestring) => CreateObject(type, itemtypestring, true); + + public static object CreateObject(Type type, string itemtypestring, bool requireConstructible) + { + // look for constructor arguments to be passed to it with the syntax type,arg1,arg2,.../ + string[] typewordargs = BaseXmlSpawner.ParseObjectArgs(itemtypestring); + + return CreateObject(type, typewordargs, requireConstructible); + } + + public static object CreateObject(Type type, string[] typewordargs, bool requireConstructible) + { + if (type == null) + { + return null; + } + + object o = null; + + int typearglen = 0; + if (typewordargs != null) + { + typearglen = typewordargs.Length; + } + + // ok, there are args in the typename, so we need to invoke the proper constructor + ConstructorInfo[] ctors = type.GetConstructors(); + + // go through all the constructors for this type + for (int i = 0; i < ctors.Length; ++i) + { + ConstructorInfo ctor = ctors[i]; + + // if requireConstructible is true, then allow either condition +#if (RESTRICTConstructible) + if (!(requireConstructible && Add.IsConstructible(ctor,requester))) + continue; +#else + if (!(requireConstructible && IsConstructible(ctor))) + { + continue; + } +#endif + + // check the parameter list of the constructor + ParameterInfo[] paramList = ctor.GetParameters(); + + // and compare with the argument list provided + if (typearglen == paramList.Length) + { + // this is a constructor that takes args and matches the number of args passed in to CreateObject + if (paramList.Length > 0) + { + object[] paramValues = null; + + try + { + paramValues = Add.ParseValues(paramList, typewordargs); + } + catch { } + + if (paramValues == null) + { + continue; + } + + // ok, have a match on args, so try to construct it + try + { + o = Activator.CreateInstance(type, paramValues); + } + catch { } + } + else + { + // zero argument constructor + try + { + o = Activator.CreateInstance(type); + } + catch { } + } + + // successfully constructed the object, otherwise try another matching constructor + if (o != null) + { + break; + } + } + } + + return o; + } + + private static void DoGlobalSectorTimer(TimeSpan delay) + { + if (m_GlobalSectorTimer != null) + { + m_GlobalSectorTimer.Stop(); + } + + m_GlobalSectorTimer = new GlobalSectorTimer(delay); + + m_GlobalSectorTimer.Start(); + } + + private class GlobalSectorTimer : Timer + { + + public GlobalSectorTimer(TimeSpan delay) : base(delay, delay) + { + } + + protected override void OnTick() + { + // check the sectors + + // check all active players + foreach (NetState state in TcpServer.Instances) + { + Mobile m = state.Mobile; + + if (m != null && (m.AccessLevel <= SmartSpawnAccessLevel || !m.Hidden)) + { + // activate any spawner in the sector they are in + if (m.Map != null && m.Map != Map.Internal) + { + Sector s = m.Map.GetSector(m.Location); + + if (s != null && GlobalSectorTable[m.Map.MapID] != null) + { + + List spawnerlist; // = GlobalSectorTable[m.Map.MapID][s]; + if (GlobalSectorTable[m.Map.MapID].TryGetValue(s, out spawnerlist) && spawnerlist != null) + { + foreach (XmlSpawner spawner in spawnerlist) + { + + if (spawner != null && !spawner.Deleted && spawner.Running && spawner.SmartSpawning && spawner.IsInactivated) + { + spawner.SmartRespawn(); + } + } + } + } + } + } + } + } + } + + public void DoSectorTimer(TimeSpan delay) + { + if (m_SectorTimer != null) + { + m_SectorTimer.Stop(); + } + + m_SectorTimer = new SectorTimer(this, delay); + + m_SectorTimer.Start(); + } + + private class SectorTimer : Timer + { + private readonly XmlSpawner m_Spawner; + + public SectorTimer(XmlSpawner spawner, TimeSpan delay) : base(delay, delay) => m_Spawner = spawner; + + protected override void OnTick() + { + // check the sectors + if (m_Spawner != null && !m_Spawner.Deleted && m_Spawner.Running && m_Spawner.IsInactivated) + { + if (m_Spawner.SmartSpawning) + { + if (m_Spawner.HasActiveSectors) + { + Stop(); + + m_Spawner.SmartRespawn(); + } + } + else + { + Stop(); + + m_Spawner.IsInactivated = false; + } + } + else + { + Stop(); + + } + } + } + + private class WarnTimer2 : Timer + { + private readonly List m_List; + + private class WarnEntry2 + { + public Point3D m_Point; + public Map m_Map; + public string m_Name; + + public WarnEntry2(Point3D p, Map map, string name) + { + m_Point = p; + m_Map = map; + m_Name = name; + } + } + + public WarnTimer2() + : base(TimeSpan.FromSeconds(1.0)) + { + m_List = new List(); + Start(); + } + + public void Add(Point3D p, Map map, string name) + { + m_List.Add(new WarnEntry2(p, map, name)); + } + + protected override void OnTick() + { + try + { + Console.WriteLine("Warning: {0} bad spawns detected, logged: 'badspawn.log'", m_List.Count); + + using (StreamWriter op = new StreamWriter("badspawn.log", true)) + { + op.WriteLine("# Bad spawns : {0}", DateTime.UtcNow); + op.WriteLine("# Format: X Y Z F Name"); + op.WriteLine(); + + foreach (WarnEntry2 e in m_List) + { + op.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}", e.m_Point.X, e.m_Point.Y, e.m_Point.Z, e.m_Map, e.m_Name); + } + + op.WriteLine(); + op.WriteLine(); + } + } + catch + { } + } + } + + public void DoTimer() + { + if (!m_Running) + { + return; + } + + int minSeconds = (int)m_MinDelay.TotalSeconds; + int maxSeconds = (int)m_MaxDelay.TotalSeconds; + + TimeSpan delay = TimeSpan.FromSeconds(Utility.RandomMinMax(minSeconds, maxSeconds)); + DoTimer(delay); + } + + public void DoTimer(TimeSpan delay) + { + if (!m_Running) + { + return; + } + + m_End = DateTime.UtcNow + delay; + + if (m_Timer != null) + { + m_Timer.Stop(); + } + + m_Timer = new SpawnerTimer(this, delay); + m_Timer.Start(); + } + + public void DoTimer2(TimeSpan delay) + { + m_DurEnd = DateTime.UtcNow + delay; + if (m_Duration > TimeSpan.FromMinutes(0) || m_durActivated) + { + if (m_DurTimer != null) + { + m_DurTimer.Stop(); + } + + m_DurTimer = new InternalTimer(this, delay); + m_DurTimer.Start(); + m_durActivated = true; + } + } + + public void DoTimer3(TimeSpan delay) + { + m_RefractEnd = DateTime.UtcNow + delay; + m_refractActivated = true; + + if (m_RefractoryTimer != null) + { + m_RefractoryTimer.Stop(); + } + + m_RefractoryTimer = new InternalTimer3(this, delay); + m_RefractoryTimer.Start(); + } + + // added the duration timer that begins on spawning + private class InternalTimer : Timer + { + private readonly XmlSpawner m_spawner; + + public InternalTimer(XmlSpawner spawner, TimeSpan delay) : base(delay) => m_spawner = spawner; + + protected override void OnTick() + { + if (m_spawner != null && !m_spawner.Deleted) + { + m_spawner.RemoveSpawnObjects(); + m_spawner.m_durActivated = false; + } + + } + } + + private class SpawnerTimer : Timer + { + private readonly XmlSpawner m_Spawner; + + public SpawnerTimer(XmlSpawner spawner, TimeSpan delay) : base(delay) => m_Spawner = spawner; + + protected override void OnTick() + { + if (m_Spawner != null && !m_Spawner.Deleted) + { + m_Spawner.OnTick(); + } + } + } + + // added the refractory timer that begins on proximity triggering + private class InternalTimer3 : Timer + { + private readonly XmlSpawner m_spawner; + + public InternalTimer3(XmlSpawner spawner, TimeSpan delay) : base(delay) => m_spawner = spawner; + + protected override void OnTick() + { + if (m_spawner != null && !m_spawner.Deleted) + { + // reenable triggering + m_spawner.m_refractActivated = false; + } + } + } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(32); // version + // version 31 + writer.Write(m_DisableGlobalAutoReset); + // Version 30 + writer.Write(m_AllowNPCTriggering); + + // Version 29 + if (m_SpawnObjects != null) + { + writer.Write(m_SpawnObjects.Count); + for (int i = 0; i < m_SpawnObjects.Count; ++i) + { + // Write the spawns per tick value + writer.Write(m_SpawnObjects[i].SpawnsPerTick); + } + } + else + { + // empty spawner + writer.Write(0); + } + + // Version 28 + if (m_SpawnObjects != null) + { + for (int i = 0; i < m_SpawnObjects.Count; ++i) + { + // Write the pack range value + writer.Write(m_SpawnObjects[i].PackRange); + } + } + + + // Version 27 + if (m_SpawnObjects != null) + { + for (int i = 0; i < m_SpawnObjects.Count; ++i) + { + // Write the disable spawn flag + writer.Write(m_SpawnObjects[i].Disabled); + } + } + + // Version 26 + writer.Write(m_SpawnOnTrigger); + + // Version 24 + if (m_SpawnObjects != null) + { + for (int i = 0; i < m_SpawnObjects.Count; ++i) + { + SpawnObject so = m_SpawnObjects[i]; + // Write the restrict kills flag + writer.Write(so.RestrictKillsToSubgroup); + // Write the clear on advance flag + writer.Write(so.ClearOnAdvance); + // Write the mindelay + writer.Write(so.MinDelay); + // Write the maxdelay + writer.Write(so.MaxDelay); + // write the next spawn time for the subgrop + writer.WriteDeltaTime(so.NextSpawn); + + } + } + + if (m_ShowBoundsItems != null && m_ShowBoundsItems.Count > 0) + { + writer.Write(true); + writer.Write(m_ShowBoundsItems); + } + else + { + // empty showbounds item list + writer.Write(false); + } + + // Version 23 + writer.Write(IsInactivated); + writer.Write(m_SmartSpawning); + // Version 22 + writer.Write(m_SkillTrigger); + writer.Write((int)m_skill_that_triggered); + writer.Write(m_FreeRun); + writer.Write(m_mob_who_triggered); + // Version 21 + writer.Write(m_DespawnTime); + // Version 20 + if (m_SpawnObjects != null) + { + for (int i = 0; i < m_SpawnObjects.Count; ++i) + { + // Write the requiresurface flag + writer.Write(m_SpawnObjects[i].RequireSurface); + } + } + // Version 19 + writer.Write(m_ConfigFile); + writer.Write(m_OnHold); + writer.Write(m_HoldSequence); + // compute the number of tags to save + int tagcount = 0; + for (int i = 0; i < m_KeywordTagList.Count; i++) + { + // only save WAIT type keywords or other keywords that have the save flag set + if ((m_KeywordTagList[i].Flags & BaseXmlSpawner.KeywordFlags.Serialize) != 0) + { + tagcount++; + } + } + writer.Write(tagcount); + // and write them out + for (int i = 0; i < m_KeywordTagList.Count; i++) + { + if ((m_KeywordTagList[i].Flags & BaseXmlSpawner.KeywordFlags.Serialize) != 0) + { + m_KeywordTagList[i].Serialize(writer); + } + } + // Version 18 + writer.Write(m_AllowGhostTriggering); + // Version 17 + // removed in version 25 + //writer.Write(m_TextEntryBook); + // Version 16 + writer.Write(m_SequentialSpawning); + // write out the remaining time until sequential reset + writer.Write(NextSeqReset); + // Write the spawn object list + if (m_SpawnObjects != null) + { + for (int i = 0; i < m_SpawnObjects.Count; ++i) + { + SpawnObject so = m_SpawnObjects[i]; + // Write the subgroup and sequential reset time + writer.Write(so.SubGroup); + writer.Write(so.SequentialResetTime); + writer.Write(so.SequentialResetTo); + writer.Write(so.KillsNeeded); + } + } + writer.Write(m_RegionName); + + // Version 15 + writer.Write(m_ExternalTriggering); + writer.Write(m_ExternalTrigger); + + // Version 14 + writer.Write(m_NoItemTriggerName); + + // Version 13 + writer.Write(m_GumpState); + + // Version 12 + int todtype = (int)m_TODMode; + writer.Write(todtype); + + // Version 11 + writer.Write(m_KillReset); + writer.Write(m_skipped); + writer.Write(m_spawncheck); + + // Version 10 + writer.Write(m_SetPropertyItem); + + // Version 9 + writer.Write(m_TriggerProbability); + + // Version 8 + writer.Write(m_MobPropertyName); + writer.Write(m_MobTriggerName); + writer.Write(m_PlayerPropertyName); + + // Version 7 + writer.Write(m_SpeechTrigger); + + // Version 6 + writer.Write(m_ItemTriggerName); + + // Version 5 + writer.Write(m_ProximityTriggerMessage); + writer.Write(m_ObjectPropertyItem); + writer.Write(m_ObjectPropertyName); + writer.Write(m_killcount); + + // Version 4 + writer.Write(m_ProximityRange); + writer.Write(m_ProximityTriggerSound); + writer.Write(m_proximityActivated); + writer.Write(m_durActivated); + writer.Write(m_refractActivated); + writer.Write(m_StackAmount); + writer.Write(m_TODStart); + writer.Write(m_TODEnd); + writer.Write(m_MinRefractory); + writer.Write(m_MaxRefractory); + if (m_refractActivated) + { + writer.Write(m_RefractEnd - DateTime.UtcNow); + } + + if (m_durActivated) + { + writer.Write(m_DurEnd - DateTime.UtcNow); + } + + // Version 3 + writer.Write(m_ShowContainerStatic); + // Version 2 + writer.Write(m_Duration); + + // Version 1 + writer.Write(m_UniqueId); + writer.Write(m_HomeRangeIsRelative); + + // Version 0 + writer.Write(m_Name); + writer.Write(m_X); + writer.Write(m_Y); + writer.Write(m_Width); + writer.Write(m_Height); + writer.Write(m_WayPoint); + writer.Write(m_Group); + writer.Write(m_MinDelay); + writer.Write(m_MaxDelay); + writer.Write(m_Count); + writer.Write(m_Team); + writer.Write(m_HomeRange); + writer.Write(m_Running); + + if (m_Running) + { + writer.Write(m_End - DateTime.UtcNow); + } + + // Write the spawn object list + int nso = 0; + if (m_SpawnObjects != null) + { + nso = m_SpawnObjects.Count; + } + + writer.Write(nso); + for (int i = 0; i < nso; ++i) + { + SpawnObject so = m_SpawnObjects[i]; + + // Write the type and maximum count + writer.Write(so.TypeName); + writer.Write(so.ActualMaxCount); + + // Write the spawned object information + writer.Write(so.SpawnedObjects.Count); + for (int x = 0; x < so.SpawnedObjects.Count; ++x) + { + object o = so.SpawnedObjects[x]; + + if (o is Item item) + { + writer.Write(item); + } + else if (o is Mobile mobile) + { + writer.Write(mobile); + } + else + { + // if this is a keyword tag then add some more info + if (o is BaseXmlSpawner.KeywordTag tag) + { + writer.Write(-1 * tag.Serial - 2); + } + else + { + writer.Write(Serial.MinusOne); + } + } + } + } + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + int version = reader.ReadInt(); + bool haveproximityrange = false; + bool hasnewobjectinfo = false; + int tmpSpawnListSize = 0; + List tmpSubGroup = null; + List tmpSequentialResetTime = null; + List tmpSequentialResetTo = null; + List tmpKillsNeeded = null; + List tmpRequireSurface = null; + List tmpRestrictKillsToSubgroup = null; + List tmpClearOnAdvance = null; + List tmpMinDelay = null; + List tmpMaxDelay = null; + List tmpNextSpawn = null; + List tmpDisableSpawn = null; + List tmpPackRange = null; + List tmpSpawnsPer = null; + + switch (version) + { + case 32: + case 31: + { + m_DisableGlobalAutoReset = reader.ReadBool(); + goto case 30; + } + case 30: + { + m_AllowNPCTriggering = reader.ReadBool(); + goto case 29; + } + case 29: + { + tmpSpawnListSize = reader.ReadInt(); + tmpSpawnsPer = new List(tmpSpawnListSize); + for (int i = 0; i < tmpSpawnListSize; ++i) + { + int spawnsper = reader.ReadInt(); + + tmpSpawnsPer.Add(spawnsper); + + } + goto case 28; + } + case 28: + { + tmpPackRange = new List(tmpSpawnListSize); + for (int i = 0; i < tmpSpawnListSize; ++i) + { + int packrange = reader.ReadInt(); + + tmpPackRange.Add(packrange); + + } + goto case 27; + } + case 27: + { + tmpDisableSpawn = new List(tmpSpawnListSize); + for (int i = 0; i < tmpSpawnListSize; ++i) + { + bool disablespawn = reader.ReadBool(); + + tmpDisableSpawn.Add(disablespawn); + + } + goto case 26; + } + case 26: + { + m_SpawnOnTrigger = reader.ReadBool(); + + if (version < 32) + { + // Delete First & Last Modified + reader.ReadDateTime(); + reader.ReadDateTime(); + } + goto case 25; + } + case 25: + { + goto case 24; + } + case 24: + { + tmpRestrictKillsToSubgroup = new List(tmpSpawnListSize); + tmpClearOnAdvance = new List(tmpSpawnListSize); + tmpMinDelay = new List(tmpSpawnListSize); + tmpMaxDelay = new List(tmpSpawnListSize); + tmpNextSpawn = new List(tmpSpawnListSize); + for (int i = 0; i < tmpSpawnListSize; ++i) + { + bool restrictkills = reader.ReadBool(); + bool clearadvance = reader.ReadBool(); + double mind = reader.ReadDouble(); + double maxd = reader.ReadDouble(); + DateTime nextspawn = reader.ReadDeltaTime(); + + tmpRestrictKillsToSubgroup.Add(restrictkills); + tmpClearOnAdvance.Add(clearadvance); + tmpMinDelay.Add(mind); + tmpMaxDelay.Add(maxd); + tmpNextSpawn.Add(nextspawn); + } + + bool hasitems = reader.ReadBool(); + + if (hasitems) + { + m_ShowBoundsItems = reader.ReadEntityList(); + } + goto case 23; + } + case 23: + { + IsInactivated = reader.ReadBool(); + SmartSpawning = reader.ReadBool(); + + goto case 22; + } + case 22: + { + SkillTrigger = reader.ReadString(); // note this will also register the skill + m_skill_that_triggered = (SkillName)reader.ReadInt(); + m_FreeRun = reader.ReadBool(); + m_mob_who_triggered = reader.ReadEntity(); + goto case 21; + } + case 21: + { + m_DespawnTime = reader.ReadTimeSpan(); + goto case 20; + } + case 20: + { + tmpRequireSurface = new List(tmpSpawnListSize); + for (int i = 0; i < tmpSpawnListSize; ++i) + { + bool requiresurface = reader.ReadBool(); + tmpRequireSurface.Add(requiresurface); + } + goto case 19; + } + case 19: + { + m_ConfigFile = reader.ReadString(); + m_OnHold = reader.ReadBool(); + m_HoldSequence = reader.ReadBool(); + + if (version < 32) + { + // // Delete First & Last Modified By + // // Delete First & Last Modified By + reader.ReadString(); + reader.ReadString(); + } + + // deserialize the keyword tag list + int tagcount = reader.ReadInt(); + m_KeywordTagList = new List(tagcount); + for (int i = 0; i < tagcount; i++) + { + BaseXmlSpawner.KeywordTag tag = new BaseXmlSpawner.KeywordTag(null, this); + tag.Deserialize(reader); + } + goto case 18; + } + case 18: + { + m_AllowGhostTriggering = reader.ReadBool(); + goto case 17; + } + case 17: + { + goto case 16; + } + case 16: + { + hasnewobjectinfo = true; + m_SequentialSpawning = reader.ReadInt(); + TimeSpan seqdelay = reader.ReadTimeSpan(); + m_SeqEnd = DateTime.UtcNow + seqdelay; + + tmpSubGroup = new List(tmpSpawnListSize); + tmpSequentialResetTime = new List(tmpSpawnListSize); + tmpSequentialResetTo = new List(tmpSpawnListSize); + tmpKillsNeeded = new List(tmpSpawnListSize); + for (int i = 0; i < tmpSpawnListSize; ++i) + { + int subgroup = reader.ReadInt(); + double resettime = reader.ReadDouble(); + int resetto = reader.ReadInt(); + int killsneeded = reader.ReadInt(); + tmpSubGroup.Add(subgroup); + tmpSequentialResetTime.Add(resettime); + tmpSequentialResetTo.Add(resetto); + tmpKillsNeeded.Add(killsneeded); + } + m_RegionName = reader.ReadString(); + goto case 15; + } + case 15: + { + m_ExternalTriggering = reader.ReadBool(); + m_ExternalTrigger = reader.ReadBool(); + goto case 14; + } + case 14: + { + m_NoItemTriggerName = reader.ReadString(); + goto case 13; + } + case 13: + { + m_GumpState = reader.ReadString(); + goto case 12; + } + case 12: + { + int todtype = reader.ReadInt(); + switch (todtype) + { + case (int)TODModeType.Gametime: + { + m_TODMode = TODModeType.Gametime; + break; + } + case (int)TODModeType.Realtime: + { + m_TODMode = TODModeType.Realtime; + break; + } + } + goto case 11; + } + case 11: + { + m_KillReset = reader.ReadInt(); + m_skipped = reader.ReadBool(); + m_spawncheck = reader.ReadInt(); + goto case 10; + } + case 10: + { + m_SetPropertyItem = reader.ReadEntity(); + goto case 9; + } + case 9: + { + m_TriggerProbability = reader.ReadDouble(); + goto case 8; + } + case 8: + { + m_MobPropertyName = reader.ReadString(); + m_MobTriggerName = reader.ReadString(); + m_PlayerPropertyName = reader.ReadString(); + goto case 7; + } + case 7: + { + m_SpeechTrigger = reader.ReadString(); + goto case 6; + } + case 6: + { + m_ItemTriggerName = reader.ReadString(); + goto case 5; + } + case 5: + { + m_ProximityTriggerMessage = reader.ReadString(); + m_ObjectPropertyItem = reader.ReadEntity(); + m_ObjectPropertyName = reader.ReadString(); + m_killcount = reader.ReadInt(); + goto case 4; + } + case 4: + { + haveproximityrange = true; + m_ProximityRange = reader.ReadInt(); + m_ProximityTriggerSound = reader.ReadInt(); + m_proximityActivated = reader.ReadBool(); + m_durActivated = reader.ReadBool(); + m_refractActivated = reader.ReadBool(); + m_StackAmount = reader.ReadInt(); + m_TODStart = reader.ReadTimeSpan(); + m_TODEnd = reader.ReadTimeSpan(); + m_MinRefractory = reader.ReadTimeSpan(); + m_MaxRefractory = reader.ReadTimeSpan(); + if (m_refractActivated) + { + TimeSpan delay = reader.ReadTimeSpan(); + DoTimer3(delay); + } + if (m_durActivated) + { + TimeSpan delay = reader.ReadTimeSpan(); + DoTimer2(delay); + } + goto case 3; + } + case 3: + { + m_ShowContainerStatic = reader.ReadEntity() ; + goto case 2; + } + case 2: + { + m_Duration = reader.ReadTimeSpan(); + goto case 1; + } + case 1: + { + m_UniqueId = reader.ReadString(); + m_HomeRangeIsRelative = reader.ReadBool(); + goto case 0; + } + case 0: + { + m_Name = reader.ReadString(); + // backward compatibility with old name storage + if (!string.IsNullOrEmpty(m_Name)) + { + Name = m_Name; + } + + m_X = reader.ReadInt(); + m_Y = reader.ReadInt(); + m_Width = reader.ReadInt(); + m_Height = reader.ReadInt(); + //we HAVE to check if the area is even or if coordinates point to the original spawner, otherwise it's custom area! + if (m_Width == m_Height && m_Width % 2 == 0 && m_X + m_Width / 2 == X && m_Y + m_Height / 2 == Y) + { + m_SpawnRange = m_Width / 2; + } + else + { + m_SpawnRange = -1; + } + + if (!haveproximityrange) + { + m_ProximityRange = -1; + } + m_WayPoint = reader.ReadEntity(); + m_Group = reader.ReadBool(); + m_MinDelay = reader.ReadTimeSpan(); + m_MaxDelay = reader.ReadTimeSpan(); + m_Count = reader.ReadInt(); + m_Team = reader.ReadInt(); + m_HomeRange = reader.ReadInt(); + m_Running = reader.ReadBool(); + + if (m_Running) + { + TimeSpan delay = reader.ReadTimeSpan(); + DoTimer(delay); + } + + // Read in the size of the spawn object list + int SpawnListSize = reader.ReadInt(); + m_SpawnObjects = new List(SpawnListSize); + for (int i = 0; i < SpawnListSize; ++i) + { + string TypeName = reader.ReadString(); + int TypeMaxCount = reader.ReadInt(); + + SpawnObject TheSpawnObject = new SpawnObject(TypeName, TypeMaxCount); + + m_SpawnObjects.Add(TheSpawnObject); + + string typeName = BaseXmlSpawner.ParseObjectType(TypeName); + + if (typeName == null || AssemblyHandler.FindTypeByName(typeName) == null && + !BaseXmlSpawner.IsTypeOrItemKeyword(typeName) && typeName.IndexOf('{') == -1 && !typeName.StartsWith("*") && !typeName.StartsWith("#")) + { + if (m_WarnTimer == null) + { + m_WarnTimer = new WarnTimer2(); + } + + m_WarnTimer.Add(Location, Map, TypeName); + + status_str = $"invalid type: {typeName}"; + } + + // Read in the number of spawns already + int SpawnedCount = reader.ReadInt(); + + TheSpawnObject.SpawnedObjects = new List(SpawnedCount); + + for (int x = 0; x < SpawnedCount; ++x) + { + int serial = reader.ReadInt(); + if (serial < -1) + { + // minusone is reserved for unknown types by default + // minustwo on is used for referencing keyword tags + int tagserial = -1 * (serial + 2); + // get the tag with that serial and add it + BaseXmlSpawner.KeywordTag t = BaseXmlSpawner.GetFromTagList(this, tagserial); + if (t != null) + { + TheSpawnObject.SpawnedObjects.Add(t); + } + } + else + { + IEntity e = World.FindEntity((Serial)(uint)serial); + + if (e != null) + { + TheSpawnObject.SpawnedObjects.Add(e); + } + } + } + } + // now have to reintegrate the later version spawnobject information into the earlier version desered objects + if (hasnewobjectinfo && tmpSpawnListSize == SpawnListSize) + { + for (int i = 0; i < SpawnListSize; ++i) + { + SpawnObject so = m_SpawnObjects[i]; + + so.SubGroup = tmpSubGroup[i]; + so.SequentialResetTime = tmpSequentialResetTime[i]; + so.SequentialResetTo = tmpSequentialResetTo[i]; + so.KillsNeeded = tmpKillsNeeded[i]; + if (version > 19) + { + so.RequireSurface = tmpRequireSurface[i]; + } + + bool restrictkills = false; + bool clearadvance = true; + double mind = -1; + double maxd = -1; + DateTime nextspawn = DateTime.MinValue; + if (version > 23) + { + restrictkills = tmpRestrictKillsToSubgroup[i]; + clearadvance = tmpClearOnAdvance[i]; + mind = tmpMinDelay[i]; + maxd = tmpMaxDelay[i]; + nextspawn = tmpNextSpawn[i]; + } + so.RestrictKillsToSubgroup = restrictkills; + so.ClearOnAdvance = clearadvance; + so.MinDelay = mind; + so.MaxDelay = maxd; + so.NextSpawn = nextspawn; + + bool disablespawn = false; + if (version > 26) + { + disablespawn = tmpDisableSpawn[i]; + } + so.Disabled = disablespawn; + + int packrange = -1; + if (version > 27) + { + packrange = tmpPackRange[i]; + } + so.PackRange = packrange; + + int spawnsper = 1; + if (version > 28) + { + spawnsper = tmpSpawnsPer[i]; + } + so.SpawnsPerTick = spawnsper; + + } + } + + break; + } + } + if (m_RegionName != null) + { + Timer.DelayCall(delegate { if (!Deleted && m_RegionName != null) + { + RegionName = m_RegionName; + } + }); + } + } + + internal string GetSerializedObjectList() + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + + foreach (SpawnObject so in m_SpawnObjects) + { + if (sb.Length > 0) + { + sb.Append(':'); // ':' Separates multiple object types + } + + sb.AppendFormat("{0}={1}", so.TypeName, so.ActualMaxCount); // '=' separates object name from maximum amount + } + + return sb.ToString(); + } + + internal string GetSerializedObjectList2() + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + + foreach (SpawnObject so in m_SpawnObjects) + { + if (sb.Length > 0) + { + sb.Append(":OBJ="); // Separates multiple object types + } + + sb.AppendFormat("{0}:MX={1}:SB={2}:RT={3}:TO={4}:KL={5}:RK={6}:CA={7}:DN={8}:DX={9}:SP={10}:PR={11}", + so.TypeName, so.ActualMaxCount, so.SubGroup, so.SequentialResetTime, so.SequentialResetTo, so.KillsNeeded, + so.RestrictKillsToSubgroup ? 1 : 0, so.ClearOnAdvance ? 1 : 0, so.MinDelay, so.MaxDelay, so.SpawnsPerTick, so.PackRange); + } + + return sb.ToString(); + } + + public class SpawnObject + { + private int m_MaxCount; + + // temporary variable used to calculate weighted spawn probabilities + public bool Available; + + public List SpawnedObjects; + public string[] PropertyArgs; + public double SequentialResetTime; + public int EntryOrder; // used for sorting + public bool RequireSurface = true; + public DateTime NextSpawn; + public bool SpawnedThisTick; + + // these are externally accessible to the SETONSPAWNENTRY keyword + public string TypeName { get; set; } + + public int MaxCount + { + get + { + if (Disabled) + { + return 0; + } + + return m_MaxCount; + } + set => m_MaxCount = value; + } + public int ActualMaxCount + { + get => m_MaxCount; + set => m_MaxCount = value; + } + public int SubGroup { get; set; } + public int SpawnsPerTick { get; set; } = 1; + public int SequentialResetTo { get; set; } + public int KillsNeeded { get; set; } + public bool RestrictKillsToSubgroup { get; set; } + public bool ClearOnAdvance { get; set; } = true; + public double MinDelay { get; set; } = -1; + public double MaxDelay { get; set; } = -1; + public bool Disabled { get; set; } + public bool Ignore { get; set; } = false; + public int PackRange { get; set; } = -1; + + // command loggable constructor + public SpawnObject(Mobile from, XmlSpawner spawner, string name, int maxamount) + { + + if (from != null && spawner != null) + { + bool found = false; + // go through the current spawner objects and see if this is a new entry + if (spawner.m_SpawnObjects != null) + { + for (int i = 0; i < spawner.m_SpawnObjects.Count; i++) + { + SpawnObject s = spawner.m_SpawnObjects[i]; + if (s != null && s.TypeName == name) + { + found = true; + break; + } + } + } + + if (!found) + { + CommandLogging.WriteLine(from, "{0} {1} added to XmlSpawner {2} '{3}' [{4}, {5}] ({6}) : {7}", from.AccessLevel, CommandLogging.Format(from), spawner.Serial, spawner.Name, spawner.GetWorldLocation().X, spawner.GetWorldLocation().Y, spawner.Map, name); + } + } + + TypeName = name; + MaxCount = maxamount; + SubGroup = 0; + SequentialResetTime = 0; + SequentialResetTo = 0; + KillsNeeded = 0; + RestrictKillsToSubgroup = false; + ClearOnAdvance = true; + SpawnedObjects = new List(); + } + + public SpawnObject(string name, int maxamount) + { + TypeName = name; + MaxCount = maxamount; + SubGroup = 0; + SequentialResetTime = 0; + SequentialResetTo = 0; + KillsNeeded = 0; + RestrictKillsToSubgroup = false; + ClearOnAdvance = true; + SpawnedObjects = new List(); + } + + public SpawnObject(string name, int maxamount, int subgroup, double sequentialresettime, int sequentialresetto, int killsneeded, + bool restrictkills, bool clearadvance, double mindelay, double maxdelay, int spawnsper, int packrange) + { + TypeName = name; + MaxCount = maxamount; + SubGroup = subgroup; + SequentialResetTime = sequentialresettime; + SequentialResetTo = sequentialresetto; + KillsNeeded = killsneeded; + RestrictKillsToSubgroup = restrictkills; + ClearOnAdvance = clearadvance; + MinDelay = mindelay; + MaxDelay = maxdelay; + SpawnsPerTick = spawnsper; + PackRange = packrange; + SpawnedObjects = new List(); + } + + internal static string GetParm(string str, string separator) + { + // find the parm separator in the string + // then look for the termination at the ':' or end of string + // and return the stuff between + string[] arg = BaseXmlSpawner.SplitString(str, separator); + //should be 2 args + if (arg.Length > 1) + { + // look for the end of parm terminator (could also be eol) + string[] parm = arg[1].Split(':'); + if (parm.Length > 0) + { + return parm[0]; + } + } + return null; + } + + internal static SpawnObject[] LoadSpawnObjectsFromString(string ObjectList) + { + // Clear the spawn object list + List NewSpawnObjects = new List(); + + if (!string.IsNullOrEmpty(ObjectList)) + { + // Split the string based on the object separator first ':' + string[] SpawnObjectList = ObjectList.Split(':'); + + // Parse each item in the array + foreach (string s in SpawnObjectList) + { + // Split the single spawn object item by the max count '=' + string[] SpawnObjectDetails = s.Split('='); + + // Should be two entries + if (SpawnObjectDetails.Length == 2) + { + // Validate the information + + // Make sure the spawn object name part has a valid length + if (SpawnObjectDetails[0].Length > 0) + { + // Make sure the max count part has a valid length + if (SpawnObjectDetails[1].Length > 0) + { + int maxCount = 1; + + try + { + maxCount = int.Parse(SpawnObjectDetails[1]); + } + catch (Exception) + { // Something went wrong, leave the default amount } + } + + // Create the spawn object and store it in the array list + SpawnObject so = new SpawnObject(SpawnObjectDetails[0], maxCount); + NewSpawnObjects.Add(so); + } + } + } + } + } + + return NewSpawnObjects.ToArray(); + } + + internal static SpawnObject[] LoadSpawnObjectsFromString2(string ObjectList) + { + // Clear the spawn object list + List NewSpawnObjects = new List(); + + // spawn object definitions will take the form typestring:MX=int:SB=int:RT=double:TO=int:KL=int + // or typestring:MX=int:SB=int:RT=double:TO=int:KL=int:OBJ=typestring... + if (!string.IsNullOrEmpty(ObjectList)) + { + string[] SpawnObjectList = BaseXmlSpawner.SplitString(ObjectList, ":OBJ="); + + // Parse each item in the array + foreach (string s in SpawnObjectList) + { + // at this point each spawn string will take the form typestring:MX=int:SB=int:RT=double:TO=int:KL=int + // Split the single spawn object item by the max count to get the typename and the remaining parms + string[] SpawnObjectDetails = BaseXmlSpawner.SplitString(s, ":MX="); + + // Should be two entries + if (SpawnObjectDetails.Length == 2) + { + // Validate the information + + // Make sure the spawn object name part has a valid length + if (SpawnObjectDetails[0].Length > 0) + { + // Make sure the parm part has a valid length + if (SpawnObjectDetails[1].Length > 0) + { + // now parse out the parms + // MaxCount + string parmstr = GetParm(s, ":MX="); + int maxCount = 1; + try { maxCount = int.Parse(parmstr); } + catch { } + + // SubGroup + parmstr = GetParm(s, ":SB="); + + int subGroup = 0; + try { subGroup = int.Parse(parmstr); } + catch { } + + // SequentialSpawnResetTime + parmstr = GetParm(s, ":RT="); + double resetTime = 0; + try { resetTime = double.Parse(parmstr); } + catch { } + + // SequentialSpawnResetTo + parmstr = GetParm(s, ":TO="); + int resetTo = 0; + try { resetTo = int.Parse(parmstr); } + catch { } + + // KillsNeeded + parmstr = GetParm(s, ":KL="); + int killsNeeded = 0; + try { killsNeeded = int.Parse(parmstr); } + catch { } + + // RestrictKills + parmstr = GetParm(s, ":RK="); + bool restrictKills = false; + if (parmstr != null) + { + try { restrictKills = int.Parse(parmstr) == 1; } + catch { } + } + + // ClearOnAdvance + parmstr = GetParm(s, ":CA="); + bool clearAdvance = true; + // if kills needed is zero, then set CA to false by default. This maintains consistency with the + // previous default behavior for old spawn specs that havent specified CA + if (killsNeeded == 0) + { + clearAdvance = false; + } + + if (parmstr != null) + { + try { clearAdvance = int.Parse(parmstr) == 1; } + catch { } + } + + // MinDelay + parmstr = GetParm(s, ":DN="); + double minD = -1; + try { minD = double.Parse(parmstr); } + catch { } + + // MaxDelay + parmstr = GetParm(s, ":DX="); + double maxD = -1; + try { maxD = double.Parse(parmstr); } + catch { } + + // SpawnsPerTick + parmstr = GetParm(s, ":SP="); + int spawnsPer = 1; + try { spawnsPer = int.Parse(parmstr); } + catch { } + + // PackRange + parmstr = GetParm(s, ":PR="); + int packRange = -1; + try { packRange = int.Parse(parmstr); } + catch { } + + // Create the spawn object and store it in the array list + SpawnObject so = new SpawnObject(SpawnObjectDetails[0], maxCount, subGroup, resetTime, resetTo, killsNeeded, + restrictKills, clearAdvance, minD, maxD, spawnsPer, packRange); + + NewSpawnObjects.Add(so); + } + } + } + } + } + + return NewSpawnObjects.ToArray(); + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlSpawnerGumps.cs b/Projects/UOContent/Engines/XMLSpawner/XmlSpawnerGumps.cs new file mode 100644 index 000000000..832509a37 --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlSpawnerGumps.cs @@ -0,0 +1,1331 @@ +#define NEWPROPSGUMP +#define BOOKTEXTENTRY +using Server.Commands; +using Server.Gumps; +using Server.Items; +using Server.Network; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Server.Mobiles; + +public class TextEntryGump : Gump +{ + private readonly XmlSpawner m_Spawner; + private readonly int m_index; + private readonly XmlSpawnerGump m_SpawnerGump; + + public TextEntryGump(XmlSpawner spawner, XmlSpawnerGump spawnergump, int index, int X, int Y) + : base(X, Y) + { + if (spawner == null || spawner.Deleted) + { + return; + } + + m_Spawner = spawner; + m_index = index; + m_SpawnerGump = spawnergump; + + AddPage(0); + + AddBackground(20, 0, 220, 354, 5054); + AddAlphaRegion(20, 0, 220, 354); + AddImageTiled(23, 5, 214, 270, 0x52); + AddImageTiled(24, 6, 213, 261, 0xBBC); + + string label = $"{spawner.Name} entry {index}"; + AddLabel(28, 10, 0x384, label); + + // OK button + AddButton(25, 325, 0xFB7, 0xFB9, 1); + // Close button + AddButton(205, 325, 0xFB1, 0xFB3, 0); + // Edit button + AddButton(100, 325, 0xEF, 0xEE, 2); + string str = null; + if (index < m_Spawner.SpawnObjects.Length) + { + str = m_Spawner.SpawnObjects[index].TypeName; + } + // main text entry area + AddTextEntry(35, 30, 200, 251, 0, 0, str); + + // editing text entry areas + // background for text entry area + AddImageTiled(23, 275, 214, 23, 0x52); + AddImageTiled(24, 276, 213, 21, 0xBBC); + AddImageTiled(23, 300, 214, 23, 0x52); + AddImageTiled(24, 301, 213, 21, 0xBBC); + + AddTextEntry(35, 275, 200, 21, 0, 1, null); + AddTextEntry(35, 300, 200, 21, 0, 2, null); + } + + public override void OnResponse(NetState state, RelayInfo info) + { + if (info == null || state?.Mobile == null) + { + return; + } + + if (m_Spawner == null || m_Spawner.Deleted) + { + return; + } + + bool update_entry = false; + bool edit_entry = false; + + switch (info.ButtonID) + { + case 0: // Close + { + break; + } + case 1: // Okay + { + update_entry = true; + break; + } + case 2: // Edit + { + edit_entry = true; + break; + } + default: + { + update_entry = true; + break; + } + } + if (edit_entry) + { + // get the old text + TextRelay entry = info.GetTextEntry(1); + string oldtext = entry.Text; + // get the new text + entry = info.GetTextEntry(2); + string newtext = entry.Text; + // make the substitution + entry = info.GetTextEntry(0); + string origtext = entry.Text; + if (origtext != null && oldtext != null && newtext != null) + { + try + { + int firstindex = origtext.IndexOf(oldtext); + if (firstindex >= 0) + { + + + int secondindex = firstindex + oldtext.Length; + + int lastindex = origtext.Length - 1; + + string editedtext; + if (firstindex > 0) + { + editedtext = origtext.Substring(0, firstindex) + newtext + origtext.Substring(secondindex, lastindex - secondindex + 1); + } + else + { + editedtext = newtext + origtext.Substring(secondindex, lastindex - secondindex + 1); + } + + if (m_index < m_Spawner.SpawnObjects.Length) + { + m_Spawner.SpawnObjects[m_index].TypeName = editedtext; + } + else + { + // Update the creature list + m_Spawner.SpawnObjects = m_SpawnerGump.CreateArray(info, state.Mobile); + } + } + } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + + } + // open a new text entry gump + state.Mobile.SendGump(new TextEntryGump(m_Spawner, m_SpawnerGump, m_index, X, Y)); + return; + } + if (update_entry) + { + TextRelay entry = info.GetTextEntry(0); + if (m_index < m_Spawner.SpawnObjects.Length) + { + m_Spawner.SpawnObjects[m_index].TypeName = entry.Text; + } + else + { + // Update the creature list + m_Spawner.SpawnObjects = m_SpawnerGump.CreateArray(info, state.Mobile); + } + } + + // open a new spawner gump + state.Mobile.SendGump(new XmlSpawnerGump(m_Spawner, X, Y, m_SpawnerGump.m_ShowGump, m_SpawnerGump.xoffset, m_SpawnerGump.page)); + } +} + +public class XmlSpawnerGump : Gump +{ + private static int nclicks; + public XmlSpawner m_Spawner; + public const int MaxSpawnEntries = 60; + private const int MaxEntriesPerPage = 15; + public int m_ShowGump; + public int xoffset; + public int initial_maxcount; + public int page; + public ReplacementEntry Rentry; + + public class ReplacementEntry + { + public string Typename; + public int Index; + public int Color; + } + + public XmlSpawnerGump(XmlSpawner spawner, int X, int Y, int extension, int textextension, int newpage) + : this(spawner, X, Y, extension, textextension, newpage, null) + { + } + + public XmlSpawnerGump(XmlSpawner spawner, int X, int Y, int extension, int textextension, int newpage, ReplacementEntry rentry) + : base(X, Y) + { + if (spawner == null || spawner.Deleted) + { + return; + } + + m_Spawner = spawner; + spawner.SpawnerGump = this; + xoffset = textextension; + initial_maxcount = spawner.MaxCount; + page = newpage; + Rentry = rentry; + + AddPage(0); + + // automatically change the gump depending on whether sequential spawning and/or subgroups are activated + + if (spawner.SequentialSpawn >= 0 || spawner.HasSubGroups() || spawner.HasIndividualSpawnTimes()) + { + // show the fully extended gump with subgroups and reset timer info + m_ShowGump = 2; + } + + if (extension > 0) + { + m_ShowGump = extension; + } + if (extension < 0) + { + m_ShowGump = 0; + } + + // if the expanded gump toggle has been activated then override the auto settings. + if (m_ShowGump > 1) + { + AddBackground(0, 0, 670 + xoffset + 30, 474, 5054); + AddAlphaRegion(0, 0, 670 + xoffset + 30, 474); + } + else + if (m_ShowGump > 0) + { + AddBackground(0, 0, 335 + xoffset, 474, 5054); + AddAlphaRegion(0, 0, 335 + xoffset, 474); + } + else + { + AddBackground(0, 0, 305 + xoffset, 474, 5054); + AddAlphaRegion(0, 0, 305 + xoffset, 474); + } + + // spawner name area + AddImageTiled(3, 5, 227, 23, 0x52); + AddImageTiled(4, 6, 225, 21, 0xBBC); + AddTextEntry(6, 5, 222, 21, 0, 999, spawner.Name); // changed from color 50 + + AddButton(5, 450, 0xFAE, 0xFAF, 4); + AddLabel(38, 450, 0x384, "Goto"); + + //AddButton(5, 428, 0xFB7, 0xFB9, 1, GumpButtonType.Reply, 0); + AddButton(5, 428, 0xFAE, 0xFAF, 1); + AddLabel(38, 428, 0x384, "Help"); + + AddButton(71, 428, 0xFB4, 0xFB6, 2); + AddLabel(104, 428, 0x384, "Bring Home"); + AddButton(71, 450, 0xFA8, 0xFAA, 3); + AddLabel(104, 450, 0x384, "Respawn"); + + // Props button + AddButton(168, 428, 0xFAB, 0xFAD, 9999); + AddLabel(201, 428, 0x384, "Props"); + + // Sort button + AddButton(168, 450, 0xFAB, 0xFAD, 702); + AddLabel(201, 450, 0x384, "Sort"); + + // Reset button + AddButton(71, 406, 0xFA2, 0xFA3, 701); + AddLabel(104, 406, 0x384, "Reset"); + + // Refresh button + AddButton(168, 406, 0xFBD, 0xFBE, 9998); + AddLabel(201, 406, 0x384, "Refresh"); + + AddButton(280, 395, m_Spawner.DisableGlobalAutoReset ? 0xD3 : 0xD2, + m_Spawner.DisableGlobalAutoReset ? 0xD2 : 0xD3, 9997); + AddLabel(263, 410, m_Spawner.DisableGlobalAutoReset ? 68 : 33, "Disable"); + AddLabel(247, 424, m_Spawner.DisableGlobalAutoReset ? 68 : 33, "TickReset"); + + // add run status display + if (m_Spawner.Running) + { + AddButton(5, 399, 0x2A4E, 0x2A3A, 700); + AddLabel(38, 406, 0x384, "On"); + } + else + { + AddButton(5, 399, 0x2A62, 0x2A3A, 700); + AddLabel(38, 406, 0x384, "Off"); + } + + // Add sequential spawn state + if (m_Spawner.SequentialSpawn >= 0) + { + AddLabel(15, 365, 33, $"{m_Spawner.SequentialSpawn}"); + } + + // Add Current / Max count labels + AddLabel(231 + xoffset, 9, 68, "Count"); + AddLabel(270 + xoffset, 9, 33, "Max"); + + if (m_ShowGump > 0) + { + // Add subgroup label + AddLabel(334 + xoffset, 9, 68, "Sub"); + } + if (m_ShowGump > 1) + { + // Add entry field labels + AddLabel(303 + xoffset, 9, 68, "Per"); + AddLabel(329 + xoffset + 30, 9, 68, "Reset"); + AddLabel(368 + xoffset + 30, 9, 68, "To"); + AddLabel(392 + xoffset + 30, 9, 68, "Kills"); + AddLabel(432 + xoffset + 30, 9, 68, "MinD"); + AddLabel(472 + xoffset + 30, 9, 68, "MaxD"); + AddLabel(515 + xoffset + 30, 9, 68, "Rng"); + AddLabel(545 + xoffset + 30, 9, 68, "RK"); + AddLabel(565 + xoffset + 30, 9, 68, "Clr"); + AddLabel(590 + xoffset + 30, 9, 68, "NextSpawn"); + } + + // add area for spawner max + AddLabel(180 + xoffset, 365, 50, "Spawner"); + AddImageTiled(267 + xoffset, 365, 35, 23, 0x52); + AddImageTiled(268 + xoffset, 365, 32, 21, 0xBBC); + AddTextEntry(273 + xoffset, 365, 33, 33, 33, 300, m_Spawner.MaxCount.ToString()); + + // add area for spawner count + AddImageTiled(231 + xoffset, 365, 35, 23, 0x52); + AddImageTiled(232 + xoffset, 365, 32, 21, 0xBBC); + AddLabel(233 + xoffset, 365, 68, m_Spawner.CurrentCount.ToString()); + + // add the status string + AddTextEntry(38, 384, 235, 33, 33, 900, m_Spawner.status_str); + // add the page buttons + for (int i = 0; i < MaxSpawnEntries / MaxEntriesPerPage; i++) + { + //AddButton(38+i*30, 365, 2206, 2206, 0, GumpButtonType.Page, 1+i); + AddButton(38 + i * 25, 365, 0x8B1 + i, 0x8B1 + i, 4000 + i); + } + + // add gump extension button + if (m_ShowGump > 1) + { + AddButton(645 + xoffset + 30, 450, 0x15E3, 0x15E7, 200); + } + else + if (m_ShowGump > 0) + { + AddButton(315 + xoffset, 450, 0x15E1, 0x15E5, 200); + } + else + { + AddButton(285 + xoffset, 450, 0x15E1, 0x15E5, 200); + } + + // add the textentry extender button + if (xoffset > 0) + { + AddButton(160, 365, 0x15E3, 0x15E7, 201); + } + else + { + AddButton(160, 365, 0x15E1, 0x15E5, 201); + } + + + for (int i = 0; i < MaxSpawnEntries; i++) + { + if (page != i / MaxEntriesPerPage) + { + continue; + } + + string str = string.Empty; + int texthue = 0; + int background = 0xBBC; + + if (i % MaxEntriesPerPage == 0) + { + //AddPage(page+1); + // add highlighted page button + AddImageTiled(35 + page * 25, 363, 25, 25, 0xBBC); + AddImage(38 + page * 25, 365, 0x8B1 + page); + } + + if (i < m_Spawner.SpawnObjects.Length) + { + // disable button + + if (m_Spawner.SpawnObjects[i].Disabled) + { + // change the background for the spawn text entry if disabled + background = 0x23F4; + AddButton(2, 22 * (i % MaxEntriesPerPage) + 34, 0x82C, 0x82C, 6000 + i); + } + else + { + AddButton(2, 22 * (i % MaxEntriesPerPage) + 36, 0x837, 0x837, 6000 + i); + } + } + + bool hasreplacement = false; + + // check for replacement entries + if (Rentry != null && Rentry.Index == i) + { + hasreplacement = true; + str = Rentry.Typename; + background = Rentry.Color; + // replacement is one time only. + Rentry = null; + } + + // increment/decrement buttons + AddButton(15, 22 * (i % MaxEntriesPerPage) + 34, 0x15E0, 0x15E4, 6 + i * 2); + AddButton(30, 22 * (i % MaxEntriesPerPage) + 34, 0x15E2, 0x15E6, 7 + i * 2); + + // categorization gump button + AddButton(171 + xoffset - 18, 22 * (i % MaxEntriesPerPage) + 34, 0x15E1, 0x15E5, 5000 + i); + + // goto spawn button + AddButton(171 + xoffset, 22 * (i % MaxEntriesPerPage) + 30, 0xFAE, 0xFAF, 1300 + i); + + // text entry gump button + AddButton(200 + xoffset, 22 * (i % MaxEntriesPerPage) + 30, 0xFAB, 0xFAD, 800 + i); + + // background for text entry area + AddImageTiled(48, 22 * (i % MaxEntriesPerPage) + 30, 133 + xoffset - 25, 23, 0x52); + AddImageTiled(49, 22 * (i % MaxEntriesPerPage) + 31, 131 + xoffset - 25, 21, background); + + if (i < m_Spawner.SpawnObjects.Length) + { + if (!hasreplacement) + { + str = m_Spawner.SpawnObjects[i].TypeName; + } + + int count = m_Spawner.SpawnObjects[i].SpawnedObjects.Count; + int max = m_Spawner.SpawnObjects[i].ActualMaxCount; + int subgrp = m_Spawner.SpawnObjects[i].SubGroup; + int spawnsper = m_Spawner.SpawnObjects[i].SpawnsPerTick; + + texthue = subgrp * 11; + if (texthue < 0) + { + texthue = 0; + } + + // Add current count + AddImageTiled(231 + xoffset, 22 * (i % MaxEntriesPerPage) + 30, 35, 23, 0x52); + AddImageTiled(232 + xoffset, 22 * (i % MaxEntriesPerPage) + 31, 32, 21, 0xBBC); + AddLabel(233 + xoffset, 22 * (i % MaxEntriesPerPage) + 30, 68, count.ToString()); + + // Add maximum count + AddImageTiled(267 + xoffset, 22 * (i % MaxEntriesPerPage) + 30, 35, 23, 0x52); + AddImageTiled(268 + xoffset, 22 * (i % MaxEntriesPerPage) + 31, 32, 21, 0xBBC); + // AddTextEntry(x,y,w,ht,color,id,str) + AddTextEntry(270 + xoffset, 22 * (i % MaxEntriesPerPage) + 30, 30, 30, 33, 500 + i, max.ToString()); + + if (m_ShowGump > 0) + { + // Add subgroup + AddImageTiled(334 + xoffset, 22 * (i % MaxEntriesPerPage) + 30, 25, 23, 0x52); + AddImageTiled(335 + xoffset, 22 * (i % MaxEntriesPerPage) + 31, 22, 21, 0xBBC); + AddTextEntry(338 + xoffset, 22 * (i % MaxEntriesPerPage) + 30, 17, 33, texthue, 600 + i, subgrp.ToString()); + } + if (m_ShowGump > 1) + { + // Add subgroup timer fields + string strrst = null; + string strto = null; + string strkill = null; + string strmind = null; + string strmaxd = null; + string strpackrange = null; + string strspawnsper = spawnsper.ToString(); + + if (m_Spawner.SpawnObjects[i].SequentialResetTime > 0 && m_Spawner.SpawnObjects[i].SubGroup > 0) + { + strrst = m_Spawner.SpawnObjects[i].SequentialResetTime.ToString(); + strto = m_Spawner.SpawnObjects[i].SequentialResetTo.ToString(); + } + if (m_Spawner.SpawnObjects[i].KillsNeeded > 0) + { + strkill = m_Spawner.SpawnObjects[i].KillsNeeded.ToString(); + } + + if (m_Spawner.SpawnObjects[i].MinDelay >= 0) + { + strmind = m_Spawner.SpawnObjects[i].MinDelay.ToString(); + } + + if (m_Spawner.SpawnObjects[i].MaxDelay >= 0) + { + strmaxd = m_Spawner.SpawnObjects[i].MaxDelay.ToString(); + } + + if (m_Spawner.SpawnObjects[i].PackRange >= 0) + { + strpackrange = m_Spawner.SpawnObjects[i].PackRange.ToString(); + } + + string strnext; + if (m_Spawner.SpawnObjects[i].NextSpawn > DateTime.UtcNow) + { + // if the next spawn tick of the spawner will occur after the subgroup is available for spawning + // then report the next spawn tick since that is the earliest that the subgroup can actually be spawned + if (DateTime.UtcNow + m_Spawner.NextSpawn > m_Spawner.SpawnObjects[i].NextSpawn) + { + strnext = m_Spawner.NextSpawn.ToString(); + } + else + { + // estimate the earliest the next spawn could occur as the first spawn tick after reaching the subgroup nextspawn + strnext = (m_Spawner.SpawnObjects[i].NextSpawn - DateTime.UtcNow + m_Spawner.NextSpawn).ToString(); + } + } + else + { + strnext = m_Spawner.NextSpawn.ToString(); + } + + int yoff = 22 * (i % MaxEntriesPerPage) + 30; + + // spawns per tick + AddImageTiled(303 + xoffset, yoff, 30, 23, 0x52); + AddImageTiled(304 + xoffset, yoff + 1, 27, 21, 0xBBC); + AddTextEntry(307 + xoffset, yoff, 22, 33, texthue, 1500 + i, strspawnsper); + // reset time + AddImageTiled(329 + xoffset + 30, yoff, 35, 23, 0x52); + AddImageTiled(330 + xoffset + 30, yoff + 1, 32, 21, 0xBBC); + AddTextEntry(333 + xoffset + 30, yoff, 27, 33, texthue, 1000 + i, strrst); + // reset to + AddImageTiled(365 + xoffset + 30, yoff, 26, 23, 0x52); + AddImageTiled(366 + xoffset + 30, yoff + 1, 23, 21, 0xBBC); + AddTextEntry(369 + xoffset + 30, yoff, 18, 33, texthue, 1100 + i, strto); + // kills needed + AddImageTiled(392 + xoffset + 30, yoff, 35, 23, 0x52); + AddImageTiled(393 + xoffset + 30, yoff + 1, 32, 21, 0xBBC); + AddTextEntry(396 + xoffset + 30, yoff, 27, 33, texthue, 1200 + i, strkill); + + // mindelay + AddImageTiled(428 + xoffset + 30, yoff, 41, 23, 0x52); + AddImageTiled(429 + xoffset + 30, yoff + 1, 38, 21, 0xBBC); + AddTextEntry(432 + xoffset + 30, yoff, 33, 33, texthue, 1300 + i, strmind); + + // maxdelay + AddImageTiled(470 + xoffset + 30, yoff, 41, 23, 0x52); + AddImageTiled(471 + xoffset + 30, yoff + 1, 38, 21, 0xBBC); + AddTextEntry(474 + xoffset + 30, yoff, 33, 33, texthue, 1400 + i, strmaxd); + + // packrange + AddImageTiled(512 + xoffset + 30, yoff, 33, 23, 0x52); + AddImageTiled(513 + xoffset + 30, yoff + 1, 30, 21, 0xBBC); + AddTextEntry(516 + xoffset + 30, yoff, 25, 33, texthue, 1600 + i, strpackrange); + + if (m_Spawner.SequentialSpawn >= 0) + { + // restrict kills button + AddButton(545 + xoffset + 30, yoff, m_Spawner.SpawnObjects[i].RestrictKillsToSubgroup ? 0xD3 : 0xD2, + m_Spawner.SpawnObjects[i].RestrictKillsToSubgroup ? 0xD2 : 0xD3, 300 + i); + + //clear on advance button for spawn entries in subgroups that require kills + AddButton(565 + xoffset + 30, yoff, m_Spawner.SpawnObjects[i].ClearOnAdvance ? 0xD3 : 0xD2, + m_Spawner.SpawnObjects[i].ClearOnAdvance ? 0xD2 : 0xD3, 400 + i); + } + + // add the next spawn time + AddLabelCropped(590 + xoffset + 30, yoff, 70, 20, 55, strnext); + } + } + + AddTextEntry(52, 22 * (i % MaxEntriesPerPage) + 31, 119 + xoffset - 25, 21, texthue, i, str); + } + } + + public XmlSpawner.SpawnObject[] CreateArray(RelayInfo info, Mobile from) + { + ArrayList SpawnObjects = new ArrayList(); + + for (int i = 0; i < MaxSpawnEntries; i++) + { + TextRelay te = info.GetTextEntry(i); + + if (te != null) + { + string str = te.Text; + + if (str.Length > 0) + { + str = str.Trim(); +#if (BOOKTEXTENTRY) + if (i < m_Spawner.SpawnObjects.Length) + { + string currenttext = m_Spawner.SpawnObjects[i].TypeName; + if (currenttext != null && currenttext.Length >= 230) + { + str = currenttext; + } + } +#endif + string typestr = BaseXmlSpawner.ParseObjectType(str); + + Type type = AssemblyHandler.FindTypeByName(typestr); + + if (type != null) + { + SpawnObjects.Add(new XmlSpawner.SpawnObject(from, m_Spawner, str, 0)); + } + else + { + // check for special keywords + if (typestr != null && (BaseXmlSpawner.IsTypeOrItemKeyword(typestr) || typestr.IndexOf("{") != -1 || typestr.StartsWith("*") || typestr.StartsWith("#"))) + { + SpawnObjects.Add(new XmlSpawner.SpawnObject(from, m_Spawner, str, 0)); + } + else + { + m_Spawner.status_str = $"{str} is not a valid type name."; + } + //from.SendMessage("{0} is not a valid type name.", str); + } + + } + } + } + + return (XmlSpawner.SpawnObject[])SpawnObjects.ToArray(typeof(XmlSpawner.SpawnObject)); + } + + public void UpdateTypeNames(Mobile from, RelayInfo info) + { + for (int i = 0; i < MaxSpawnEntries; i++) + { + TextRelay te = info.GetTextEntry(i); + + if (te != null) + { + string str = te.Text; + + if (str.Length > 0) + { + str = str.Trim(); + if (i < m_Spawner.SpawnObjects.Length) + { + // check to see if the existing typename is longer than the max textentry buffer + // if it is then dont update it since we will assume that the textentry has truncated the actual string + // that could be longer than the buffer if booktextentry is used + +#if (BOOKTEXTENTRY) + string currentstr = m_Spawner.SpawnObjects[i].TypeName; + if (currentstr != null && currentstr.Length < 230) +#endif + { + if (m_Spawner.SpawnObjects[i].TypeName != str) + { + CommandLogging.WriteLine( + from, + "{0} {1} changed XmlSpawner {2} '{3}' [{4}, {5}] ({6}) : {7} to {8}", + from.AccessLevel, + CommandLogging.Format(from), + m_Spawner.Serial, + m_Spawner.Name, + m_Spawner.GetWorldLocation().X, + m_Spawner.GetWorldLocation().Y, + m_Spawner.Map, + m_Spawner.SpawnObjects[i].TypeName, + str + ); + + } + + m_Spawner.SpawnObjects[i].TypeName = str; + } + } + } + } + } + } + + public static void RefreshSpawnerGumps(Mobile from) + { + if (from == null) + { + return; + } + + NetState ns = from.NetState; + + if (ns?.Gumps != null) + { + ArrayList refresh = new ArrayList(); + + foreach (Gump g in ns.Gumps) + { + // clear the gump status on the spawner associated with the gump + if (g is XmlSpawnerGump xg && xg.m_Spawner != null) + { + refresh.Add(xg); + } + } + + // close all of the currently opened spawner gumps + from.CloseGump(); + + // reopen the closed gumps from the gump collection + foreach (XmlSpawnerGump g in refresh) + { + // reopen a new gump for the spawner + if (g.m_Spawner != null) + { + // flag the current gump on the spawner as closed + g.m_Spawner.GumpReset = true; + + XmlSpawnerGump xg = new XmlSpawnerGump(g.m_Spawner, g.X, g.Y, g.m_ShowGump, g.xoffset, g.page, g.Rentry); + + from.SendGump(xg); + } + } + } + } + + private static bool ValidGotoObject(Mobile from, object o) + { + if (o is Item i) + { + if (!i.Deleted && i.Map != null && i.Map != Map.Internal) + { + return true; + } + + if (from != null && !from.Deleted) + { + from.SendMessage("{0} is not available", i); + } + } + else if (o is Mobile m) + { + if (!m.Deleted && m.Map != null && m.Map != Map.Internal) + { + return true; + } + + if (from != null && !from.Deleted) + { + from.SendMessage("{0} is not available", m); + } + } + + return false; + } + + public override void OnResponse(NetState state, RelayInfo info) + { + if (m_Spawner == null || m_Spawner.Deleted || state == null || info == null) + { + if (m_Spawner != null) + { + m_Spawner.SpawnerGump = null; + } + + return; + } + + // Get the current name + TextRelay tr = info.GetTextEntry(999); + if (tr != null) + { + m_Spawner.Name = tr.Text; + } + + // update typenames of the spawn objects based upon the current text entry strings + UpdateTypeNames(state.Mobile, info); + + // Update the creature list + m_Spawner.SpawnObjects = CreateArray(info, state.Mobile); + + if (m_Spawner.SpawnObjects == null) + { + m_Spawner.SpawnerGump = null; + return; + } + + for (int i = 0; i < m_Spawner.SpawnObjects.Length; i++) + { + if (page != i / MaxEntriesPerPage) + { + continue; + } + + // check the max count entry + TextRelay temcnt = info.GetTextEntry(500 + i); + if (temcnt != null) + { + int maxval = 0; + try { maxval = Convert.ToInt32(temcnt.Text, 10); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + if (maxval < 0) + { + maxval = 0; + } + + m_Spawner.SpawnObjects[i].MaxCount = maxval; + } + + if (m_ShowGump > 0) + { + // check the subgroup entry + TextRelay tegrp = info.GetTextEntry(600 + i); + if (tegrp != null) + { + int grpval = 0; + try { grpval = Convert.ToInt32(tegrp.Text, 10); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + if (grpval < 0) + { + grpval = 0; + } + + m_Spawner.SpawnObjects[i].SubGroup = grpval; + } + } + + if (m_ShowGump > 1) + { + // note, while these values can be entered in any spawn entry, they will only be assigned to the subgroup leader + int subgroupindex = m_Spawner.GetCurrentSequentialSpawnIndex(m_Spawner.SpawnObjects[i].SubGroup); + TextRelay tegrp; + + if (subgroupindex >= 0 && subgroupindex < m_Spawner.SpawnObjects.Length) + { + // check the *reset time* entry + tegrp = info.GetTextEntry(1000 + i); + if (tegrp?.Text != null && tegrp.Text.Length > 0) + { + double grpval = 0; + try { grpval = Convert.ToDouble(tegrp.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + if (grpval < 0) + { + grpval = 0; + } + + m_Spawner.SpawnObjects[i].SequentialResetTime = 0; + + m_Spawner.SpawnObjects[subgroupindex].SequentialResetTime = grpval; + } + // check the *reset to* entry + tegrp = info.GetTextEntry(1100 + i); + if (tegrp?.Text != null && tegrp.Text.Length > 0) + { + int grpval = 0; + try { grpval = Convert.ToInt32(tegrp.Text, 10); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + if (grpval < 0) + { + grpval = 0; + } + + m_Spawner.SpawnObjects[subgroupindex].SequentialResetTo = grpval; + } + // check the kills entry + tegrp = info.GetTextEntry(1200 + i); + if (tegrp?.Text != null && tegrp.Text.Length > 0) + { + int grpval = 0; + try { grpval = Convert.ToInt32(tegrp.Text, 10); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + if (grpval < 0) + { + grpval = 0; + } + + m_Spawner.SpawnObjects[subgroupindex].KillsNeeded = grpval; + } + } + + // check the mindelay + tegrp = info.GetTextEntry(1300 + i); + if (tegrp != null) + { + if (!string.IsNullOrEmpty(tegrp.Text)) + { + double grpval = -1; + try { grpval = Convert.ToDouble(tegrp.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + if (grpval < 0) + { + grpval = -1; + } + + // if this value has changed, then update the next spawn time + if (grpval != m_Spawner.SpawnObjects[i].MinDelay) + { + m_Spawner.SpawnObjects[i].MinDelay = grpval; + m_Spawner.RefreshNextSpawnTime(m_Spawner.SpawnObjects[i]); + } + } + else + { + m_Spawner.SpawnObjects[i].MinDelay = -1; + m_Spawner.SpawnObjects[i].MaxDelay = -1; + m_Spawner.RefreshNextSpawnTime(m_Spawner.SpawnObjects[i]); + } + } + + // check the maxdelay + tegrp = info.GetTextEntry(1400 + i); + if (tegrp != null) + { + if (!string.IsNullOrEmpty(tegrp.Text)) + { + double grpval = -1; + try { grpval = Convert.ToDouble(tegrp.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + if (grpval < 0) + { + grpval = -1; + } + + // if this value has changed, then update the next spawn time + if (grpval != m_Spawner.SpawnObjects[i].MaxDelay) + { + m_Spawner.SpawnObjects[i].MaxDelay = grpval; + m_Spawner.RefreshNextSpawnTime(m_Spawner.SpawnObjects[i]); + } + } + else + { + m_Spawner.SpawnObjects[i].MinDelay = -1; + m_Spawner.SpawnObjects[i].MaxDelay = -1; + m_Spawner.RefreshNextSpawnTime(m_Spawner.SpawnObjects[i]); + } + } + + // check the spawns per tick + tegrp = info.GetTextEntry(1500 + i); + if (tegrp != null) + { + if (!string.IsNullOrEmpty(tegrp.Text)) + { + int grpval = 1; + try { grpval = int.Parse(tegrp.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + if (grpval < 0) + { + grpval = 1; + } + + // if this value has changed, then update the next spawn time + if (grpval != m_Spawner.SpawnObjects[i].SpawnsPerTick) + { + m_Spawner.SpawnObjects[i].SpawnsPerTick = grpval; + } + } + else + { + m_Spawner.SpawnObjects[i].SpawnsPerTick = 1; + } + } + + // check the packrange + tegrp = info.GetTextEntry(1600 + i); + if (tegrp != null) + { + if (!string.IsNullOrEmpty(tegrp.Text)) + { + int grpval = 1; + try { grpval = int.Parse(tegrp.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + if (grpval < 0) + { + grpval = 1; + } + + // if this value has changed, then update + if (grpval != m_Spawner.SpawnObjects[i].PackRange) + { + m_Spawner.SpawnObjects[i].PackRange = grpval; + } + } + else + { + m_Spawner.SpawnObjects[i].PackRange = -1; + } + } + } + } + + // Update the maxcount + TextRelay temax = info.GetTextEntry(300); + if (temax != null) + { + int maxval = 0; + try { maxval = Convert.ToInt32(temax.Text, 10); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + if (maxval < 0) + { + maxval = 0; + } + + // if the maxcount of the spawner has been altered external to the interface (e.g. via props, or by the running spawner itself + // then that change will override the text entry + if (m_Spawner.MaxCount == initial_maxcount) + { + m_Spawner.MaxCount = maxval; + } + } + + switch (info.ButtonID) + { + case 0: // Close + { + // clear any text entry books + m_Spawner.DeleteTextEntryBook(); + // and reset the gump status + m_Spawner.GumpReset = true; + + return; + } + case 1: // Help + { + break; + } + case 2: // Bring everything home + { + m_Spawner.BringToHome(); + break; + } + case 3: // Complete respawn + { + m_Spawner.TryRespawn(); + //m_Spawner.AdvanceSequential(); + m_Spawner.m_killcount = 0; + break; + } + case 4: // Goto + { + state.Mobile.Location = m_Spawner.Location; + state.Mobile.Map = m_Spawner.Map; + break; + } + case 200: // gump extension + { + state.Mobile.SendGump(m_ShowGump > 1 + ? new XmlSpawnerGump(m_Spawner, X, Y, -1, xoffset, page) + : new XmlSpawnerGump(m_Spawner, X, Y, m_ShowGump + 2, xoffset, page)); + return; + } + case 201: // gump text extension + { + state.Mobile.SendGump(xoffset > 0 + ? new XmlSpawnerGump(m_Spawner, X, Y, m_ShowGump, 0, page) + : new XmlSpawnerGump(m_Spawner, X, Y, m_ShowGump, 250, page)); + return; + } + case 700: // Start/stop spawner + { + m_Spawner.Running = !m_Spawner.Running; + break; + } + case 701: // Complete reset + { + m_Spawner.Reset(); + break; + } + case 702: // Sort spawns + { + m_Spawner.SortSpawns(); + break; + } + case 900: // empty the status string + { + m_Spawner.status_str = ""; + break; + } + case 9997: + { + m_Spawner.DisableGlobalAutoReset = !m_Spawner.DisableGlobalAutoReset; + break; + } + case 9998: // refresh the gump + { + state.Mobile.SendGump(new XmlSpawnerGump(m_Spawner, X, Y, m_ShowGump, xoffset, page)); + return; + } + case 9999: + { + // Show the props window for the spawner, as well as a new gump + state.Mobile.SendGump(new XmlSpawnerGump(m_Spawner, X, Y, m_ShowGump, xoffset, page)); + + state.Mobile.SendGump(new XmlPropertiesGump(state.Mobile, m_Spawner)); + return; + } + default: + { + // check the restrict kills flag + if (info.ButtonID >= 300 && info.ButtonID < 300 + MaxSpawnEntries) + { + int index = info.ButtonID - 300; + if (index < m_Spawner.SpawnObjects.Length) + { + m_Spawner.SpawnObjects[index].RestrictKillsToSubgroup = !m_Spawner.SpawnObjects[index].RestrictKillsToSubgroup; + } + } + else if (info.ButtonID >= 400 && info.ButtonID < 400 + MaxSpawnEntries) + { + int index = info.ButtonID - 400; + if (index < m_Spawner.SpawnObjects.Length) + { + m_Spawner.SpawnObjects[index].ClearOnAdvance = !m_Spawner.SpawnObjects[index].ClearOnAdvance; + } + } + else if (info.ButtonID >= 800 && info.ButtonID < 800 + MaxSpawnEntries) + { + // open the text entry gump + int index = info.ButtonID - 800; + // open a text entry gump +#if (BOOKTEXTENTRY) + // display a new gump + XmlSpawnerGump newgump = new XmlSpawnerGump(m_Spawner, X, Y, m_ShowGump, xoffset, page); + state.Mobile.SendGump(newgump); + + // is there an existing book associated with the gump? + if (m_Spawner.m_TextEntryBook == null) + { + m_Spawner.m_TextEntryBook = new List(); + } + + object[] args = new object[6]; + + args[0] = m_Spawner; + args[1] = index; + args[2] = X; + args[3] = Y; + args[4] = m_ShowGump; + args[5] = page; + + XmlTextEntryBook book = new XmlTextEntryBook(0, string.Empty, m_Spawner.Name, 20, true); + + m_Spawner.m_TextEntryBook.Add(book); + + book.Title = $"Entry {index}"; + book.Author = m_Spawner.Name; + + // fill the contents of the book with the current text entry data + string text = string.Empty; + if (m_Spawner.SpawnObjects != null && index < m_Spawner.SpawnObjects.Length) + { + text = m_Spawner.SpawnObjects[index].TypeName; + } + book.FillTextEntryBook(text); + + // put the book at the location of the player so that it can be opened, but drop it below visible range + book.Visible = false; + book.Movable = false; + book.MoveToWorld(new Point3D(state.Mobile.Location.X, state.Mobile.Location.Y, state.Mobile.Location.Z - 100), state.Mobile.Map); + + // and open it + book.OnDoubleClick(state.Mobile); + +#else + state.Mobile.SendGump(new TextEntryGump(m_Spawner,this, index, this.X, this.Y)); +#endif + return; + } + else if (info.ButtonID >= 1300 && info.ButtonID < 1300 + MaxSpawnEntries) + { + nclicks++; + // find the location of the spawn at the specified index + int index = info.ButtonID - 1300; + if (index < m_Spawner.SpawnObjects.Length) + { + int scount = m_Spawner.SpawnObjects[index].SpawnedObjects.Count; + if (scount > 0) + { + object so = m_Spawner.SpawnObjects[index].SpawnedObjects[nclicks % scount]; + + if (ValidGotoObject(state.Mobile, so)) + { + IPoint3D o = so as IPoint3D; + + if (o != null) + { + Map m = m_Spawner.Map; + + if (o is Item item) + { + m = item.Map; + } + + if (o is Mobile mobile) + { + m = mobile.Map; + } + + state.Mobile.Location = new Point3D(o); + state.Mobile.Map = m; + } + } + } + } + } + else if (info.ButtonID >= 4000 && info.ButtonID < 4001 + MaxSpawnEntries / MaxEntriesPerPage) + { + // which page + page = info.ButtonID - 4000; + + } + else if (info.ButtonID >= 6000 && info.ButtonID < 6000 + MaxSpawnEntries) + { + int index = info.ButtonID - 6000; + + if (index < m_Spawner.SpawnObjects.Length) + { + m_Spawner.SpawnObjects[index].Disabled = !m_Spawner.SpawnObjects[index].Disabled; + + // clear any current spawns on the disabled entry + if (m_Spawner.SpawnObjects[index].Disabled) + { + m_Spawner.RemoveSpawnObjects(m_Spawner.SpawnObjects[index]); + } + } + } + else if (info.ButtonID >= 5000 && info.ButtonID < 5000 + MaxSpawnEntries) + { + int i = info.ButtonID - 5000; + + string categorystring = null; + string entrystring = null; + + TextRelay te = info.GetTextEntry(i); + + if (te?.Text != null) + { + // get the string + + string[] cargs = te.Text.Split(','); + + // parse out any comma separated args + categorystring = cargs[0]; + + entrystring = te.Text; + } + + if (string.IsNullOrEmpty(categorystring)) + { + + XmlSpawnerGump newg = new XmlSpawnerGump(m_Spawner, X, Y, m_ShowGump, xoffset, page); + state.Mobile.SendGump(newg); + + // if no string has been entered then just use the full categorized add gump + state.Mobile.CloseGump(); + state.Mobile.SendGump(new XmlCategorizedAddGump(state.Mobile, i, newg)); + } + else + { + // use the XmlPartialCategorizedAddGump + state.Mobile.CloseGump(); + + //Type [] types = (Type[])XmlPartialCategorizedAddGump.Match(categorystring).ToArray(typeof(Type)); + ArrayList types = XmlPartialCategorizedAddGump.Match(categorystring); + + + ReplacementEntry re = new ReplacementEntry + { + Typename = entrystring, + Index = i, + Color = 0x1436 + }; + + XmlSpawnerGump newg = new XmlSpawnerGump(m_Spawner, X, Y, m_ShowGump, xoffset, page, re); + + state.Mobile.SendGump(new XmlPartialCategorizedAddGump(state.Mobile, categorystring, 0, types, true, i, newg)); + + state.Mobile.SendGump(newg); + } + + return; + } + else + { + // up and down arrows + int buttonID = info.ButtonID - 6; + int index = buttonID / 2; + int type = buttonID % 2; + + TextRelay entry = info.GetTextEntry(index); + + if (entry != null && entry.Text.Length > 0) + { + string entrystr = entry.Text; + +#if (BOOKTEXTENTRY) + if (index < m_Spawner.SpawnObjects.Length) + { + string str = m_Spawner.SpawnObjects[index].TypeName; + + if (str != null && str.Length >= 230) + { + entrystr = str; + } + } +#endif + if (type == 0) // Add creature + { + m_Spawner.AddSpawnObject(entrystr); + } + else // Remove creatures + { + m_Spawner.DeleteSpawnObject(state.Mobile, entrystr); + + } + } + } + break; + } + } + + state.Mobile.SendGump(new XmlSpawnerGump(m_Spawner, X, Y, m_ShowGump, xoffset, page, Rentry)); + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlSpawnerSkillCheck.cs b/Projects/UOContent/Engines/XMLSpawner/XmlSpawnerSkillCheck.cs new file mode 100644 index 000000000..314551e44 --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlSpawnerSkillCheck.cs @@ -0,0 +1,296 @@ +using System.Collections; +using Server.Items; +using CPA = Server.CommandPropertyAttribute; +using Server.Misc; + +namespace Server.Mobiles; + +public class XmlSpawnerSkillCheck +{ + // alternate skillcheck hooks to replace those in SkillCheck.cs + public static bool Mobile_SkillCheckLocation(Mobile from, SkillName skillName, double minSkill, double maxSkill) + { + Skill skill = from.Skills[skillName]; + + if (skill == null) + { + return false; + } + + // call the default skillcheck handler + bool success = SkillCheck.Mobile_SkillCheckLocation( from, skillName, minSkill, maxSkill); + + // call the xmlspawner skillcheck handler + CheckSkillUse(from, skill, success); + + return success; + } + + public static bool Mobile_SkillCheckDirectLocation(Mobile from, SkillName skillName, double chance) + { + Skill skill = from.Skills[skillName]; + + if (skill == null) + { + return false; + } + + // call the default skillcheck handler + bool success = SkillCheck.Mobile_SkillCheckDirectLocation( from, skillName, chance); + + // call the xmlspawner skillcheck handler + CheckSkillUse(from, skill, success); + + return success; + } + + public static bool Mobile_SkillCheckTarget(Mobile from, SkillName skillName, object target, double minSkill, double maxSkill) + { + Skill skill = from.Skills[skillName]; + + if (skill == null) + { + return false; + } + + // call the default skillcheck handler + bool success = SkillCheck.Mobile_SkillCheckTarget( from, skillName, target, minSkill, maxSkill); + + // call the xmlspawner skillcheck handler + CheckSkillUse(from, skill, success); + + return success; + } + + public static bool Mobile_SkillCheckDirectTarget(Mobile from, SkillName skillName, object target, double chance) + { + Skill skill = from.Skills[skillName]; + + if (skill == null) + { + return false; + } + + // call the default skillcheck handler + bool success = SkillCheck.Mobile_SkillCheckDirectTarget( from, skillName, target, chance); + + // call the xmlspawner skillcheck handler + CheckSkillUse(from, skill, success); + + return success; + } + + + public class RegisteredSkill + { + public const int MaxSkills = 52; + public const SkillName Invalid = (SkillName)(-1); + + public object target; + public SkillName sid; + + // note the extra skill MaxSkills +1 is used for any unknown skill that falls outside of the known 52 + private static ArrayList[] m_FeluccaSkillList = new ArrayList[MaxSkills+1]; + private static ArrayList[] m_TrammelSkillList = new ArrayList[MaxSkills+1]; + private static ArrayList[] m_MalasSkillList = new ArrayList[MaxSkills+1]; + private static ArrayList[] m_IlshenarSkillList = new ArrayList[MaxSkills+1]; + private static ArrayList[] m_TokunoSkillList = new ArrayList[MaxSkills+1]; + + // primary function that returns the list of objects (spawners) that are associated with a given skillname by map + public static ArrayList TriggerList(SkillName index, Map map) + { + if (map == null || map == Map.Internal) + { + return null; + } + + ArrayList[] maplist; + + // get the list for the specified map + + if (map == Map.Felucca) + { + maplist = m_FeluccaSkillList; + } + else if (map == Map.Ilshenar) + { + maplist = m_IlshenarSkillList; + } + else if (map == Map.Malas) + { + maplist = m_MalasSkillList; + } + else if (map == Map.Trammel) + { + maplist = m_TrammelSkillList; + } + else if (map == Map.Tokuno) + { + maplist = m_TokunoSkillList; + } + else + { + return null; + } + + // is it one of the standard 52 skills + if ((int)index >= 0 && (int)index < MaxSkills) + { + return maplist[(int)index] ??= new ArrayList(); + } + + // otherwise pull it out of the final slot for unknown skills. I dont know of a condition that would lead to + // additional skills being registered but it will support them if they are + return maplist[MaxSkills] ??= new ArrayList(); + } + } + + public static void RegisterSkillTrigger(object o, SkillName s, Map map) + { + if (o == null || s == RegisteredSkill.Invalid) + { + return; + } + + // go through the list and if the spawner is not on it yet, then add it + bool found = false; + + ArrayList skilllist = RegisteredSkill.TriggerList(s, map); + + if (skilllist == null) + { + return; + } + + foreach(RegisteredSkill rs in skilllist) + { + if (rs.target == o && rs.sid == s) + { + found = true; + // dont register a skill if it is already on the list for this spawner + break; + } + } + + // if it hasnt already been added to the list, then add it + if (!found) + { + RegisteredSkill newrs = new RegisteredSkill(); + newrs.target = o; + newrs.sid = s; + + skilllist.Add(newrs); + + } + } + + public static void UnRegisterSkillTrigger(object o, SkillName s, Map map, bool all) + { + if (o == null || s == RegisteredSkill.Invalid) + { + return; + } + + // go through the list and if the spawner is on it regardless of the skill registered, then remove it + if (all) + { + for(int i = 0;i 0) + { + foreach(XmlAttachment a in list) + { + if (a != null && !a.Deleted && a.HandlesOnSkillUse) + { + a.OnSkillUse(m, skill, success); + } + } + } + */ + + // then check for registered skills + ArrayList skilllist = RegisteredSkill.TriggerList(skill.SkillName, m.Map); + + if (skilllist == null) + { + return; + } + + // determine whether there are any registered objects for this skill + foreach(RegisteredSkill rs in skilllist) + { + if (rs.sid == skill.SkillName) + { + // if so then invoke their skill handlers + if (rs.target is XmlSpawner spawner) + { + if (spawner.HandlesOnSkillUse) + { + // call the spawner handler + spawner.OnSkillUse(m, skill, success); + } + } else + if (rs.target is IXmlQuest quest) + { + if (quest.HandlesOnSkillUse) + { + // call the xmlquest handler + quest.OnSkillUse(m, skill, success); + } + } + } + } + } + +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlTextEntryBook.cs b/Projects/UOContent/Engines/XMLSpawner/XmlTextEntryBook.cs new file mode 100644 index 000000000..971d1d6c4 --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlTextEntryBook.cs @@ -0,0 +1,80 @@ +namespace Server.Items; + +public class XmlTextEntryBook : BaseBook +{ + public XmlTextEntryBook(int itemID, string title, string author, int pageCount, bool writable) : base(itemID, title, author, pageCount, writable) + { + } + + public XmlTextEntryBook(Serial serial) : base(serial) + { + } + + public void FillTextEntryBook(string text) + { + int pagenum = 0; + int current = 0; + + // break up the text into single line length pieces + while (text != null && current < text.Length) + { + int lineCount = 10; + string[] lines = new string[lineCount]; + + // place the line on the page + for (int i = 0; i < lineCount; i++) + { + if (current < text.Length) + { + // make each line 25 chars long + int length = text.Length - current; + if (length > 20) + { + length = 20; + } + + lines[i] = text.Substring(current, length); + current += length; + } + else + { + // fill up the remaining lines + lines[i] = string.Empty; + } + } + + if (pagenum >= PagesCount) + { + return; + } + + Pages[pagenum].Lines = lines; + pagenum++; + } + // empty the remaining contents + for (int j = pagenum; j < PagesCount; j++) + { + if (Pages[j].Lines.Length > 0) + { + for (int i = 0; i < Pages[j].Lines.Length; i++) + { + Pages[j].Lines[i] = string.Empty; + } + } + } + } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + writer.Write(0); // version + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + reader.ReadInt(); + + Delete(); + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlUtils/WriteMulti.cs b/Projects/UOContent/Engines/XMLSpawner/XmlUtils/WriteMulti.cs new file mode 100644 index 000000000..8992ed537 --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlUtils/WriteMulti.cs @@ -0,0 +1,468 @@ +using System.IO; +using System.Collections; +using Server.Items; +using Server.Mobiles; + +namespace Server.Engines.XmlSpawner2; + +public class WriteMulti +{ + private class TileEntry + { + public int ID; + public int X; + public int Y; + public int Z; + + public TileEntry(int id, int x, int y, int z) + { + ID = id; + X = x; + Y = y; + Z = z; + } + } + + public static void Initialize() + { + + CommandSystem.Register("WriteMulti", XmlSpawner.DiskAccessLevel, WriteMulti_OnCommand); + } + + [Usage("WriteMulti [zmin zmax][-noitems][-nostatics][-nomultis][-noaddons][-invisible]")] + [Description("Creates a multi text file from the objects within the targeted area. The min/max z range can also be specified.")] + public static void WriteMulti_OnCommand(CommandEventArgs e) + { + if (e == null || e.Mobile == null) + { + return; + } + + if (e.Mobile.AccessLevel < XmlSpawner.DiskAccessLevel) + { + e.Mobile.SendMessage("You do not have rights to perform this command."); + return; + } + + if (e.Arguments != null && e.Arguments.Length < 1) + { + e.Mobile.SendMessage("Usage: {0} [zmin zmax][-noitems][-nostatics][-nomultis][-noaddons][-invisible]", e.Command); + return; + } + + string filename = e.Arguments[0]; + + int zmin = int.MinValue; + int zmax = int.MinValue; + bool includeitems = true; + bool includestatics = true; + bool includemultis = true; + bool includeaddons = true; + bool includeinvisible = false; + + if (e.Arguments.Length > 1) + { + int index = 1; + while (index < e.Arguments.Length) + { + if (e.Arguments[index] == "-noitems") + { + includeitems = false; + index++; + } + else if (e.Arguments[index] == "-nostatics") + { + includestatics = false; + index++; + } + else if (e.Arguments[index] == "-nomultis") + { + includemultis = false; + index++; + } + else if (e.Arguments[index] == "-noaddons") + { + includeaddons = false; + index++; + } + else if (e.Arguments[index] == "-invisible") + { + includeinvisible = true; + index++; + } + else + { + try + { + zmin = int.Parse(e.Arguments[index++]); + zmax = int.Parse(e.Arguments[index++]); + } + catch + { + e.Mobile.SendMessage("{0} : Invalid zmin zmax arguments", e.Command); + return; + } + } + } + } + + string dirname; + if (Directory.Exists(XmlSpawner.XmlSpawnDir) && filename != null && !filename.StartsWith("/") && !filename.StartsWith("\\")) + { + // put it in the defaults directory if it exists + dirname = $"{XmlSpawner.XmlSpawnDir}/{filename}"; + } + else + { + // otherwise just put it in the main installation dir + dirname = filename; + } + + // check to see if the file already exists and can be written to by the owner + if (File.Exists(dirname)) + { + + // check the file + try + { + StreamReader op = new StreamReader(dirname, false); + + if (op == null) + { + e.Mobile.SendMessage("Cannot access file {0}", dirname); + return; + } + + string line = op.ReadLine(); + + op.Close(); + + // check the first line + if (line != null && line.Length > 0) + { + + string[] args = line.Split(" ".ToCharArray(), 3); + if (args == null || args.Length < 3) + { + e.Mobile.SendMessage("Cannot overwrite file {0} : not owner", dirname); + return; + } + + if (args[2] != e.Mobile.Name) + { + e.Mobile.SendMessage("Cannot overwrite file {0} : not owner", dirname); + return; + } + } + else + { + e.Mobile.SendMessage("Cannot overwrite file {0} : not owner", dirname); + return; + } + + } + catch + { + e.Mobile.SendMessage("Cannot overwrite file {0}", dirname); + return; + } + + } + + DefineMultiArea(e.Mobile, dirname, zmin, zmax, includeitems, includestatics, includemultis, includeinvisible, includeaddons); + } + + public static void DefineMultiArea(Mobile m, string dirname, int zmin, int zmax, bool includeitems, bool includestatics, + bool includemultis, bool includeinvisible, bool includeaddons) + { + BoundingBoxPicker.Begin( + m, + (map, start, end) => DefineMultiArea_Callback( + m, + map, + start, + end, + dirname, + zmin, + zmax, + includeitems, + includestatics, + includemultis, + includeinvisible, + includeaddons + ) + ); + } + + private static void DefineMultiArea_Callback( + Mobile from, + Map map, + Point3D start, + Point3D end, + string dirname, + int zmin, + int zmax, + bool includeitems, + bool includestatics, + bool includemultis, + bool includeinvisible, + bool includeaddons + ) + { + if (from != null && map != null) + { + ArrayList itemlist = new ArrayList(); + ArrayList staticlist = new ArrayList(); + ArrayList tilelist = new ArrayList(); + + int sx = start.X > end.X ? end.X : start.X; + int sy = start.Y > end.Y ? end.Y : start.Y; + int ex = start.X < end.X ? end.X : start.X; + int ey = start.Y < end.Y ? end.Y : start.Y; + + // find all of the world-placed items within the specified area + if (includeitems) + { + // make the first pass for items only + IPooledEnumerable eable = map.GetItemsInBounds(new Rectangle2D(sx, sy, ex - sx + 1, ey - sy + 1)); + + foreach (Item item in eable) + { + // is it within the bounding area + if (item.Parent == null && (zmin == int.MinValue || item.Location.Z >= zmin && item.Location.Z <= zmax)) + { + // add the item + if ((includeinvisible || item.Visible) && item.ItemID <= 16383) + { + itemlist.Add(item); + } + } + } + + eable.Free(); + + int searchrange = 100; + + // make the second expanded pass to pick up addon components and multi components + eable = map.GetItemsInBounds(new Rectangle2D(sx - searchrange, sy - searchrange, ex - sy + searchrange * 2 + 1, + ey - sy + searchrange * 2 + 1)); + + foreach (Item item in eable) + { + // is it within the bounding area + if (item.Parent == null) + { + + if (item is BaseAddon addon && includeaddons) + { + // go through all of the addon components + foreach (AddonComponent c in addon.Components) + { + int x = c.X; + int y = c.Y; + int z = c.Z; + + if ((includeinvisible || addon.Visible) && (addon.ItemID <= 16383 || includemultis) && + x >= sx && x <= ex && y >= sy && y <= ey && (zmin == int.MinValue || z >= zmin && z <= zmax)) + { + itemlist.Add(c); + } + } + } + + if (item is BaseMulti multi && includemultis) + { + // go through all of the multi components + MultiComponentList mcl = multi.Components; + if (mcl != null && mcl.List != null) + { + for (int i = 0; i < mcl.List.Length; i++) + { + MultiTileEntry t = mcl.List[i]; + + int x = t.OffsetX + multi.X; + int y = t.OffsetY + multi.Y; + int z = t.OffsetZ + multi.Z; + int itemID = t.ItemId & 0x3FFF; + + if (x >= sx && x <= ex && y >= sy && y <= ey && (zmin == int.MinValue || z >= zmin && z <= zmax)) + { + tilelist.Add(new TileEntry(itemID, x, y, z)); + } + } + + } + } + } + } + + eable.Free(); + } + + // find all of the static tiles within the specified area + if (includestatics) + { + // count the statics + for (int x = sx; x < ex; x++) + { + for (int y = sy; y < ey; y++) + { + StaticTile[] statics = map.Tiles.GetStaticTiles(x, y, false); + + for (int j = 0; j < statics.Length; j++) + { + if (zmin == int.MinValue || statics[j].Z >= zmin && statics[j].Z <= zmax) + { + staticlist.Add(new TileEntry(statics[j].ID & 0x3FFF, x, y, statics[j].Z)); + } + } + } + } + } + + int nstatics = staticlist.Count; + int nitems = itemlist.Count; + int ntiles = tilelist.Count; + + int ntotal = nitems + nstatics + ntiles; + + int ninvisible = 0; + int nmultis = ntiles; + int naddons = 0; + + foreach (Item item in itemlist) + { + int x = item.X - from.X; + int y = item.Y - from.Y; + int z = item.Z - from.Z; + + if (item.ItemID > 16383) + { + nmultis++; + } + if (!item.Visible) + { + ninvisible++; + } + if (item is BaseAddon || item is AddonComponent) + { + naddons++; + } + } + + try + { + // open the file, overwrite any previous contents + StreamWriter op = new StreamWriter(dirname, false); + + if (op != null) + { + // write the header + op.WriteLine("1 version {0}", from.Name); + op.WriteLine("{0} num components", ntotal); + + // write out the items + foreach (Item item in itemlist) + { + + int x = item.X - from.X; + int y = item.Y - from.Y; + int z = item.Z - from.Z; + + if (item.Hue > 0) + { + // format is x y z visible hue + op.WriteLine("{0} {1} {2} {3} {4} {5}", item.ItemID, x, y, z, item.Visible ? 1 : 0, item.Hue); + } + else + { + // format is x y z visible + op.WriteLine("{0} {1} {2} {3} {4}", item.ItemID, x, y, z, item.Visible ? 1 : 0); + } + } + + if (includestatics) + { + foreach (TileEntry s in staticlist) + { + int x = s.X - from.X; + int y = s.Y - from.Y; + int z = s.Z - from.Z; + int ID = s.ID; + op.WriteLine("{0} {1} {2} {3} {4}", ID, x, y, z, 1); + } + } + + if (includemultis) + { + foreach (TileEntry s in tilelist) + { + int x = s.X - from.X; + int y = s.Y - from.Y; + int z = s.Z - from.Z; + int ID = s.ID; + op.WriteLine("{0} {1} {2} {3} {4}", ID, x, y, z, 1); + } + } + } + + op.Close(); + } + catch + { + from.SendMessage("Error writing multi file {0}", dirname); + return; + } + + from.SendMessage(66, "WriteMulti results:"); + + if (includeitems) + { + from.SendMessage(66, "Included {0} items", nitems); + + if (includemultis) + { + from.SendMessage("{0} multis", nmultis); + } + else + { + from.SendMessage(33, "Ignored multis"); + } + + if (includeinvisible) + { + from.SendMessage("{0} invisible", ninvisible); + } + else + { + from.SendMessage(33, "Ignored invisible"); + } + + if (includeaddons) + { + from.SendMessage("{0} addons", naddons); + } + else + { + from.SendMessage(33, "Ignored addons"); + } + + } + else + { + from.SendMessage(33, "Ignored items"); + } + + if (includestatics) + { + from.SendMessage(66, "Included {0} statics", nstatics); + } + else + { + from.SendMessage(33, "Ignored statics"); + } + + from.SendMessage(66, "Saved {0} components to {1}", ntotal, dirname); + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlAdd.cs b/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlAdd.cs new file mode 100644 index 000000000..eac9420f2 --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlAdd.cs @@ -0,0 +1,1774 @@ +using Server.Accounting; +using Server.Gumps; +using Server.Items; +using Server.Network; +using Server.Targeting; +using System; +using System.Collections; +using System.Data; +using System.IO; + +namespace Server.Mobiles; + +public class XmlSpawnerDefaults +{ + public class DefaultEntry + { + public string AccountName; + public string PlayerName; + public TimeSpan MinDelay = TimeSpan.FromMinutes(5); + public TimeSpan MaxDelay = TimeSpan.FromMinutes(10); + public TimeSpan RefractMin = TimeSpan.FromMinutes(0); + public TimeSpan RefractMax = TimeSpan.FromMinutes(0); + public TimeSpan TODStart = TimeSpan.FromMinutes(0); + public TimeSpan TODEnd = TimeSpan.FromMinutes(0); + public TimeSpan Duration = TimeSpan.FromMinutes(0); + public TimeSpan DespawnTime = TimeSpan.FromHours(0); + public bool Group; + public int Team; + public int ProximitySound = 0x1F4; + public string SpeechTrigger; + public string SkillTrigger; + public int SequentialSpawn = -1; + public bool HomeRangeIsRelative = true; + public int SpawnRange = 5; + public int HomeRange = 5; + public int ProximityRange = -1; + public XmlSpawner.TODModeType TODMode = XmlSpawner.TODModeType.Realtime; + public int KillReset = 1; + public string SpawnerName = "Spawner"; + public bool AllowGhostTrig; + public bool AllowNPCTrig; + public bool SpawnOnTrigger; + public bool SmartSpawning; + public bool ExternalTriggering; + public string TriggerOnCarried; + public string NoTriggerOnCarried; + public string ProximityMsg; + public double TriggerProbability = 1; + public string PlayerTriggerProp; + public string TriggerObjectProp; + public string DefsExt; + public string[] NameList; + public bool[] SelectionList; + public int AddGumpX = 440; + public int AddGumpY; + public int SpawnerGumpX; + public int SpawnerGumpY; + public int FindGumpX; + public int FindGumpY; + + // these are additional defaults that are not set by XmlAdd but can be used by other routines such as the custom properties gump to determine + // whether properties have been changed from spawner default values + public bool Running = true; + + public bool AutoNumber; + public int AutoNumberValue; + + public XmlAddCAGCategory CurrentCategory; + public int CurrentCategoryPage; + public int CategorySelectionIndex = -1; + + public XmlSpawner LastSpawner; + public Map StartingMap; + public Point3D StartingLoc; + public bool ShowExtension; + + public bool IgnoreUpdate; + } + public static ArrayList DefaultEntryList; + + public static DefaultEntry GetDefaults(string account, string name) + { + // find the default entry corresponding to the account and username + if (DefaultEntryList != null) + { + for (int i = 0; i < DefaultEntryList.Count; i++) + { + DefaultEntry entry = (DefaultEntry)DefaultEntryList[i]; + if (entry != null && string.Compare(entry.PlayerName, name, true) == 0 && string.Compare(entry.AccountName, account, true) == 0) + { + return entry; + } + } + } + // if not found then add one + DefaultEntry newentry = new DefaultEntry + { + PlayerName = name, + AccountName = account + }; + if (DefaultEntryList == null) + { + DefaultEntryList = new ArrayList(); + } + + DefaultEntryList.Add(newentry); + return newentry; + } + + public static void RestoreDefs(DefaultEntry defs) + { + if (defs == null) + { + return; + } + + defs.MinDelay = TimeSpan.FromMinutes(5); + defs.MaxDelay = TimeSpan.FromMinutes(10); + defs.RefractMin = TimeSpan.FromMinutes(0); + defs.RefractMax = TimeSpan.FromMinutes(0); + defs.TODStart = TimeSpan.FromMinutes(0); + defs.TODEnd = TimeSpan.FromMinutes(0); + defs.Duration = TimeSpan.FromMinutes(0); + defs.DespawnTime = TimeSpan.FromHours(0); + defs.Group = false; + defs.Team = 0; + defs.ProximitySound = 0x1F4; + defs.SpeechTrigger = null; + defs.SkillTrigger = null; + defs.SequentialSpawn = -1; + defs.HomeRangeIsRelative = true; + defs.SpawnRange = 5; + defs.HomeRange = 5; + defs.ProximityRange = -1; + defs.TODMode = XmlSpawner.TODModeType.Realtime; + defs.KillReset = 1; + defs.SpawnerName = "Spawner"; + defs.AllowGhostTrig = false; + defs.AllowNPCTrig = false; + defs.SpawnOnTrigger = false; + defs.SmartSpawning = false; + defs.ExternalTriggering = false; + defs.TriggerOnCarried = null; + defs.NoTriggerOnCarried = null; + defs.ProximityMsg = null; + defs.TriggerProbability = 1; + defs.PlayerTriggerProp = null; + defs.TriggerObjectProp = null; + defs.DefsExt = null; + defs.AddGumpX = 440; + defs.AddGumpY = 0; + defs.SpawnerGumpX = 0; + defs.SpawnerGumpY = 0; + defs.FindGumpX = 0; + defs.FindGumpY = 0; + defs.AutoNumber = false; + defs.AutoNumberValue = 0; + + if (defs.SelectionList != null) + { + Array.Clear(defs.SelectionList, 0, defs.SelectionList.Length); + } + + if (defs.NameList != null) + { + Array.Clear(defs.NameList, 0, defs.NameList.Length); + } + } +} + +public class XmlAddGump : Gump +{ + private const int MaxEntries = 40; + private const int MaxEntriesPerColumn = 20; + private const string DefsDataSetName = "Defs"; + private const string DefsTablePointName = "Values"; + private const string DefsDir = "SpawnerDefs"; + + private readonly Mobile m_From; + + public XmlSpawnerDefaults.DefaultEntry defs; + + private string NameListToString() + { + if (defs.NameList == null || defs.NameList.Length == 0) + { + return "0"; + } + + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + sb.AppendFormat("{0}", defs.NameList.Length); + for (int i = 0; i < defs.NameList.Length; i++) + { + sb.AppendFormat(":{0}", defs.NameList[i]); + } + return sb.ToString(); + } + + private string SelectionListToString() + { + if (defs.SelectionList == null || defs.SelectionList.Length == 0) + { + return "0"; + } + + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + sb.AppendFormat("{0}", defs.SelectionList.Length); + for (int i = 0; i < defs.SelectionList.Length; i++) + { + sb.AppendFormat(":{0}", defs.SelectionList[i] ? 1 : 0); + } + return sb.ToString(); + } + + private static string[] StringToNameList(string namelist) + { + string[] newlist = new string[MaxEntries]; + string[] tmplist = namelist.Split(':'); + for (int i = 1; i < tmplist.Length; i++) + { + if (i - 1 >= newlist.Length) + { + break; + } + + newlist[i - 1] = tmplist[i]; + } + return newlist; + } + + private static bool[] StringToSelectionList(string selectionlist) + { + bool[] newlist = new bool[MaxEntries]; + string[] tmplist = selectionlist.Split(':'); + for (int i = 1; i < tmplist.Length; i++) + { + if (i - 1 >= newlist.Length) + { + break; + } + + if (tmplist[i] == "1") + { + newlist[i - 1] = true; + } + else + { + newlist[i - 1] = false; + } + } + return newlist; + } + + private void DoSaveDefs(Mobile from, string filename) + { + if (filename == null || filename.Length <= 0) + { + return; + } + + // Create the data set + DataSet ds = new DataSet(DefsDataSetName); + + // Load the data set up + ds.Tables.Add(DefsTablePointName); + + // Create spawn point schema + //ds.Tables[DefsTablePointName].Columns.Add("AccountName"); + //ds.Tables[DefsTablePointName].Columns.Add("PlayerName"); + ds.Tables[DefsTablePointName].Columns.Add("MinDelay"); + ds.Tables[DefsTablePointName].Columns.Add("MaxDelay"); + ds.Tables[DefsTablePointName].Columns.Add("SpawnRange"); + ds.Tables[DefsTablePointName].Columns.Add("HomeRange"); + ds.Tables[DefsTablePointName].Columns.Add("MinRefractory"); + ds.Tables[DefsTablePointName].Columns.Add("MaxRefractory"); + ds.Tables[DefsTablePointName].Columns.Add("TODStart"); + ds.Tables[DefsTablePointName].Columns.Add("TODEnd"); + ds.Tables[DefsTablePointName].Columns.Add("Duration"); + ds.Tables[DefsTablePointName].Columns.Add("DespawnTime"); + ds.Tables[DefsTablePointName].Columns.Add("RelativeHome"); + ds.Tables[DefsTablePointName].Columns.Add("IsGroup"); + ds.Tables[DefsTablePointName].Columns.Add("Team"); + ds.Tables[DefsTablePointName].Columns.Add("ProximityTriggerSound"); + ds.Tables[DefsTablePointName].Columns.Add("SpeechTrigger"); + ds.Tables[DefsTablePointName].Columns.Add("SkillTrigger"); + ds.Tables[DefsTablePointName].Columns.Add("SequentialSpawn"); + ds.Tables[DefsTablePointName].Columns.Add("ProximityRange"); + ds.Tables[DefsTablePointName].Columns.Add("TODMode"); + ds.Tables[DefsTablePointName].Columns.Add("KillReset"); + ds.Tables[DefsTablePointName].Columns.Add("SpawnerName"); + ds.Tables[DefsTablePointName].Columns.Add("AllowGhost"); + ds.Tables[DefsTablePointName].Columns.Add("AllowNPC"); + ds.Tables[DefsTablePointName].Columns.Add("SpawnOnTrigger"); + ds.Tables[DefsTablePointName].Columns.Add("SmartSpawn"); + ds.Tables[DefsTablePointName].Columns.Add("ExtTrig"); + ds.Tables[DefsTablePointName].Columns.Add("TrigOnCarried"); + ds.Tables[DefsTablePointName].Columns.Add("NoTrigOnCarried"); + ds.Tables[DefsTablePointName].Columns.Add("ProximityMessage"); + ds.Tables[DefsTablePointName].Columns.Add("TrigProb"); + ds.Tables[DefsTablePointName].Columns.Add("PlayerTrigProp"); + ds.Tables[DefsTablePointName].Columns.Add("TrigObjectProp"); + //ds.Tables[DefsTablePointName].Columns.Add("DefsExt"); + ds.Tables[DefsTablePointName].Columns.Add("NameList"); + ds.Tables[DefsTablePointName].Columns.Add("SelectionList"); + ds.Tables[DefsTablePointName].Columns.Add("AddGumpX"); + ds.Tables[DefsTablePointName].Columns.Add("AddGumpY"); + ds.Tables[DefsTablePointName].Columns.Add("SpawnerGumpX"); + ds.Tables[DefsTablePointName].Columns.Add("SpawnerGumpY"); + ds.Tables[DefsTablePointName].Columns.Add("FindGumpX"); + ds.Tables[DefsTablePointName].Columns.Add("FindGumpY"); + ds.Tables[DefsTablePointName].Columns.Add("AutoNumber"); + ds.Tables[DefsTablePointName].Columns.Add("AutoNumberValue"); + + // Create a new data row + DataRow dr = ds.Tables[DefsTablePointName].NewRow(); + + // Populate the data + //dr["AccountName"] = (string)defs.AccountName; + //dr["PlayerName"] = (string)defs.PlayerName; + dr["SpawnerName"] = defs.SpawnerName; + dr["MinDelay"] = defs.MinDelay.TotalMinutes; + dr["MaxDelay"] = defs.MaxDelay.TotalMinutes; + dr["SpawnRange"] = defs.SpawnRange; + dr["HomeRange"] = defs.HomeRange; + dr["RelativeHome"] = defs.HomeRangeIsRelative; + dr["IsGroup"] = defs.Group; + dr["Team"] = defs.Team; + dr["MinRefractory"] = defs.RefractMin.TotalMinutes; + dr["MaxRefractory"] = defs.RefractMax.TotalMinutes; + dr["TODStart"] = defs.TODStart.TotalMinutes; + dr["TODEnd"] = defs.TODEnd.TotalMinutes; + dr["TODMode"] = defs.TODMode; + dr["Duration"] = defs.Duration.TotalMinutes; + dr["DespawnTime"] = defs.Duration.TotalHours; + dr["ProximityRange"] = defs.ProximityRange; + dr["ProximityTriggerSound"] = defs.ProximitySound; + dr["ProximityMessage"] = defs.ProximityMsg; + dr["SpeechTrigger"] = defs.SpeechTrigger; + dr["SkillTrigger"] = defs.SkillTrigger; + dr["SequentialSpawn"] = defs.SequentialSpawn; + dr["KillReset"] = defs.KillReset; + dr["TrigProb"] = defs.TriggerProbability; + dr["AllowGhost"] = defs.AllowGhostTrig; + dr["AllowNPC"] = defs.AllowNPCTrig; + dr["SpawnOnTrigger"] = defs.SpawnOnTrigger; + dr["SmartSpawn"] = defs.SmartSpawning; + dr["ExtTrig"] = defs.ExternalTriggering; + dr["TrigOnCarried"] = defs.TriggerOnCarried; + dr["NoTrigOnCarried"] = defs.NoTriggerOnCarried; + dr["PlayerTrigProp"] = defs.PlayerTriggerProp; + dr["TrigObjectProp"] = defs.TriggerObjectProp; + dr["NameList"] = NameListToString(); + dr["SelectionList"] = SelectionListToString(); + dr["AddGumpX"] = defs.AddGumpX; + dr["AddGumpY"] = defs.AddGumpY; + dr["SpawnerGumpX"] = defs.SpawnerGumpX; + dr["SpawnerGumpY"] = defs.SpawnerGumpY; + dr["FindGumpX"] = defs.FindGumpX; + dr["FindGumpY"] = defs.FindGumpY; + dr["AutoNumber"] = defs.AutoNumber; + dr["AutoNumberValue"] = defs.AutoNumberValue; + + // Add the row the the table + ds.Tables[DefsTablePointName].Rows.Add(dr); + + // Write out the file + bool file_error = false; + + var dirname = Directory.Exists(DefsDir) ? $"{DefsDir}/{filename}.defs" : $"{filename}.defs"; + + try + { + ds.WriteXml(dirname); + } + catch { file_error = true; } + + if (file_error) + { + if (from != null && !from.Deleted) + { + from.SendMessage("Error trying to save to file {0}", dirname); + } + + return; + } + + if (from != null && !from.Deleted) + { + from.SendMessage("Saved defs to file {0}", dirname); + } + } + + private void DoLoadDefs(Mobile from, string filename) + { + if (filename == null || filename.Length <= 0) + { + return; + } + + string dirname; + if (Directory.Exists(DefsDir)) + { + // look for it in the defaults directory + dirname = $"{DefsDir}/{filename}.defs"; + // Check if the file exists + if (File.Exists(dirname) == false) + { + // didnt find it so just look in the main install dir + dirname = $"{filename}.defs"; + } + } + else + { + // look in the main installation dir + dirname = $"{filename}.defs"; + } + // Check if the file exists + if (File.Exists(dirname)) + { + FileStream fs = null; + try + { + fs = File.Open(dirname, FileMode.Open, FileAccess.Read); + } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + + if (fs == null) + { + from.SendMessage("Unable to open {0} for loading", dirname); + return; + } + + // Create the data set + DataSet ds = new DataSet(DefsDataSetName); + + // Read in the file + //ds.ReadXml(e.Arguments[0].ToString()); + bool fileerror = false; + try + { + ds.ReadXml(fs); + } + catch { fileerror = true; } + // close the file + fs.Close(); + if (fileerror) + { + if (from != null && !from.Deleted) + { + from.SendMessage(33, "Error reading defs file {0}", dirname); + } + + return; + } + + // Check that at least a single table was loaded + if (ds.Tables.Count > 0) + { + // Add each spawn point to the current map + if (ds.Tables[DefsTablePointName] != null && ds.Tables[DefsTablePointName].Rows.Count > 0) + { + //foreach(DataRow dr in ds.Tables[DefsTablePointName].Rows){ + DataRow dr = ds.Tables[DefsTablePointName].Rows[0]; + + try { defs.SpawnerName = (string)dr["SpawnerName"]; } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + + double mindelay = defs.MinDelay.TotalMinutes; + try { mindelay = double.Parse((string)dr["MinDelay"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + defs.MinDelay = TimeSpan.FromMinutes(mindelay); + + double maxdelay = defs.MaxDelay.TotalMinutes; + try { maxdelay = double.Parse((string)dr["MaxDelay"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + defs.MaxDelay = TimeSpan.FromMinutes(maxdelay); + + try { defs.SpawnRange = int.Parse((string)dr["SpawnRange"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.HomeRange = int.Parse((string)dr["HomeRange"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.HomeRangeIsRelative = bool.Parse((string)dr["RelativeHome"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.Group = bool.Parse((string)dr["IsGroup"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.Team = int.Parse((string)dr["Team"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + + double minrefract = defs.RefractMin.TotalMinutes; + try { minrefract = double.Parse((string)dr["MinRefractory"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + defs.RefractMin = TimeSpan.FromMinutes(minrefract); + + double maxrefract = defs.RefractMax.TotalMinutes; + try { maxrefract = double.Parse((string)dr["MaxRefractory"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + defs.RefractMax = TimeSpan.FromMinutes(maxrefract); + + double todstart = defs.TODStart.TotalMinutes; + try { todstart = double.Parse((string)dr["TODStart"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + defs.TODStart = TimeSpan.FromMinutes(todstart); + + double todend = defs.TODEnd.TotalMinutes; + try { todend = double.Parse((string)dr["TODEnd"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + defs.TODEnd = TimeSpan.FromMinutes(todend); + + string todmode = null; + try { todmode = (string)dr["TODMode"]; } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + if (todmode != null) + { + if (todmode == "Realtime") + { + defs.TODMode = XmlSpawner.TODModeType.Realtime; + } + else + if (todmode == "Gametime") + { + defs.TODMode = XmlSpawner.TODModeType.Gametime; + } + } + + double duration = defs.Duration.TotalMinutes; + try { duration = double.Parse((string)dr["Duration"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + defs.Duration = TimeSpan.FromMinutes(duration); + + double despawnTime = defs.DespawnTime.TotalHours; + try { despawnTime = double.Parse((string)dr["DespawnTime"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + defs.DespawnTime = TimeSpan.FromHours(despawnTime); + + try { defs.ProximityRange = int.Parse((string)dr["ProximityRange"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.ProximitySound = int.Parse((string)dr["ProximityTriggerSound"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.ProximityMsg = (string)dr["ProximityMessage"]; } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.SpeechTrigger = (string)dr["SpeechTrigger"]; } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.SkillTrigger = (string)dr["SkillTrigger"]; } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.SequentialSpawn = int.Parse((string)dr["SequentialSpawn"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.KillReset = int.Parse((string)dr["KillReset"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.TriggerProbability = double.Parse((string)dr["TrigProb"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.AllowGhostTrig = bool.Parse((string)dr["AllowGhost"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.AllowNPCTrig = bool.Parse((string)dr["AllowNPC"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.SpawnOnTrigger = bool.Parse((string)dr["SpawnOnTrigger"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.SmartSpawning = bool.Parse((string)dr["SmartSpawn"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.ExternalTriggering = bool.Parse((string)dr["ExtTrig"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.TriggerOnCarried = (string)dr["TrigOnCarried"]; } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.NoTriggerOnCarried = (string)dr["NoTrigOnCarried"]; } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.PlayerTriggerProp = (string)dr["PlayerTrigProp"]; } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.TriggerObjectProp = (string)dr["TrigObjectProp"]; } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + + try { defs.NameList = StringToNameList((string)dr["NameList"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.SelectionList = StringToSelectionList((string)dr["SelectionList"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.AddGumpX = int.Parse((string)dr["AddGumpX"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.AddGumpY = int.Parse((string)dr["AddGumpY"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.SpawnerGumpX = int.Parse((string)dr["SpawnerGumpX"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.SpawnerGumpY = int.Parse((string)dr["SpawnerGumpY"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.FindGumpX = int.Parse((string)dr["FindGumpX"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.FindGumpY = int.Parse((string)dr["FindGumpY"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.AutoNumber = bool.Parse((string)dr["AutoNumber"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + try { defs.AutoNumberValue = int.Parse((string)dr["AutoNumberValue"]); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + + if (from != null && !from.Deleted) + { + from.SendMessage("Loaded defs from file {0}", dirname); + } + } + } + } + else + { + if (from != null && !from.Deleted) + { + from.SendMessage(33, "File not found: {0}", dirname); + } + } + } + + public static void Initialize() + { + CommandSystem.Register("XmlAdd", AccessLevel.GameMaster, XmlAdd_OnCommand); + } + + [Usage("XmlAdd [-defaults]")] + [Description("Opens a gump that can add Xmlspawners with specified default settings")] + public static void XmlAdd_OnCommand(CommandEventArgs e) + { + Account acct = e.Mobile.Account as Account; + int x = 440; + int y = 0; + XmlSpawnerDefaults.DefaultEntry defs = null; + if (acct != null) + { + defs = XmlSpawnerDefaults.GetDefaults(acct.ToString(), e.Mobile.Name); + } + + if (defs != null) + { + x = defs.AddGumpX; + y = defs.AddGumpY; + } + // Check if there is an argument provided (load criteria) + try + { + // Check if there is an argument provided (load criteria) + for (int nxtarg = 0; nxtarg < e.Arguments.Length; nxtarg++) + { + // is it a defaults option? + if (e.Arguments[nxtarg].ToLower() == "-defaults") + { + XmlSpawnerDefaults.RestoreDefs(defs); + if (defs != null) + { + x = defs.AddGumpX; + y = defs.AddGumpY; + } + } + } + } + catch (Exception ex) { Diagnostics.ExceptionLogging.LogException(ex); } + + e.Mobile.SendGump(new XmlAddGump(e.Mobile, e.Mobile.Location, e.Mobile.Map, true, false, x, y)); + + } + + public XmlAddGump(Mobile from, Point3D startloc, Map startmap, bool firststart, bool extension, int gumpx, int gumpy) : base(gumpx, gumpy) + { + if (from == null || from.Deleted) + { + return; + } + + defs = null; + + m_From = from; + + // read the text entries for default values + Account acct = from.Account as Account; + if (acct != null) + { + defs = XmlSpawnerDefaults.GetDefaults(acct.ToString(), from.Name); + } + + if (defs == null) + { + return; + } + + if (firststart) + { + defs.StartingMap = from.Map; + defs.StartingLoc = from.Location; + } + else + { + defs.StartingMap = startmap; + defs.StartingLoc = startloc; + } + + defs.IgnoreUpdate = false; + defs.ShowExtension = extension; + + if (defs.SelectionList == null) + { + defs.SelectionList = new bool[MaxEntries]; + } + if (defs.NameList == null) + { + defs.NameList = new string[MaxEntries]; + } + + + // prepare the page + + AddPage(0); + if (defs.ShowExtension) + { + AddBackground(0, 0, 520, 500, 5054); + AddAlphaRegion(0, 0, 520, 500); + } + else + { + AddBackground(0, 0, 200, 500, 5054); + AddAlphaRegion(0, 0, 200, 500); + } + + var y = 3; + var yinc = 20; + // add the min/maxdelay entries + AddImageTiled(5, y, 40, 19, 0xBBC); + AddTextEntry(5, y, 40, 19, 0, 100, defs.MinDelay.TotalMinutes.ToString()); + AddLabel(45, y, 0x384, "MinDelay(m)"); + + AddImageTiled(105, y, 40, 19, 0xBBC); + AddTextEntry(105, y, 40, 19, 0, 101, defs.MaxDelay.TotalMinutes.ToString()); + AddLabel(145, y, 0x384, "MaxDelay(m)"); + + y += yinc; + AddImageTiled(5, y, 40, 19, 0xBBC); + AddTextEntry(5, y, 40, 19, 0, 107, defs.HomeRange.ToString()); + AddLabel(45, y, 0x384, "HomeRng"); + + AddImageTiled(105, y, 40, 19, 0xBBC); + AddTextEntry(105, y, 40, 19, 0, 108, defs.SpawnRange.ToString()); + AddLabel(145, y, 0x384, "SpawnRng"); + + y += yinc; + AddImageTiled(5, y, 40, 19, 0xBBC); + AddTextEntry(5, y, 40, 19, 0, 109, defs.ProximityRange.ToString()); + AddLabel(45, y, 0x384, "ProxRng"); + + AddImageTiled(105, y, 40, 19, 0xBBC); + AddTextEntry(105, y, 40, 19, 0, 110, defs.Team.ToString()); + AddLabel(145, y, 0x384, "Team"); + + y += yinc; + AddImageTiled(5, y, 40, 19, 0xBBC); + AddTextEntry(5, y, 40, 19, 0, 113, defs.KillReset.ToString()); + AddLabel(45, y, 0x384, "KillReset"); + + AddImageTiled(105, y, 40, 19, 0xBBC); + AddTextEntry(105, y, 40, 19, 0, 121, defs.TriggerProbability.ToString()); + AddLabel(145, y, 0x384, "TrigProb"); + + y += yinc; + AddImageTiled(5, y, 40, 19, 0xBBC); + AddTextEntry(5, y, 40, 19, 0, 111, defs.Duration.TotalMinutes.ToString()); + AddLabel(45, y, 0x384, "Duration(m)"); + + AddImageTiled(105, y, 40, 19, 0xBBC); + AddTextEntry(105, y, 40, 19, 0, 112, defs.ProximitySound.ToString()); + AddLabel(145, y, 0x384, "ProxSnd"); + + y += yinc; + AddImageTiled(5, y, 40, 19, 0xBBC); + AddTextEntry(5, y, 40, 19, 0, 102, defs.RefractMin.TotalMinutes.ToString()); + AddLabel(45, y, 0x384, "MinRefr(m)"); + + AddImageTiled(105, y, 40, 19, 0xBBC); + AddTextEntry(105, y, 40, 19, 0, 103, defs.RefractMax.TotalMinutes.ToString()); + AddLabel(145, y, 0x384, "MaxRefr(m)"); + + y += yinc; + AddImageTiled(5, y, 40, 19, 0xBBC); + AddTextEntry(5, y, 40, 19, 0, 104, defs.TODStart.TotalHours.ToString()); + AddLabel(45, y, 0x384, "TODStart(h)"); + + AddImageTiled(105, y, 40, 19, 0xBBC); + AddTextEntry(105, y, 40, 19, 0, 105, defs.TODEnd.TotalHours.ToString()); + AddLabel(145, y, 0x384, "TODEnd(h)"); + + y += yinc; + AddImageTiled(5, y, 40, 19, 0xBBC); + AddTextEntry(5, y, 40, 19, 0, 123, defs.DespawnTime.TotalHours.ToString()); + AddLabel(45, y, 0x384, "Despawn(h)"); + + + // AllowNPC + AddLabel(125, y, 0x384, "AllowNPC"); + AddCheck(105, y, 0xD2, 0xD3, defs.AllowNPCTrig, 312); + + //y = 164; + yinc = 21; + y += yinc; + // TOD + if (defs.TODMode == XmlSpawner.TODModeType.Gametime) + { + AddLabel(25, y, 0x384, "GameTOD"); + } + else + if (defs.TODMode == XmlSpawner.TODModeType.Realtime) + { + AddLabel(25, y, 0x384, "RealTOD"); + } + + AddButton(5, y, 0xD3, 0xD3, 306); + + + // Sequentialspawn + AddLabel(125, y, 0x384, "SeqSpawn"); + AddCheck(105, y, 0xD2, 0xD3, defs.SequentialSpawn == 0, 307); + + y += yinc; + // IsGroup + AddLabel(25, y, 0x384, "Group"); + AddCheck(5, y, 0xD2, 0xD3, defs.Group, 304); + + // HomeRangeRelative + AddLabel(125, y, 0x384, "HomeRngRel"); + AddCheck(105, y, 0xD2, 0xD3, defs.HomeRangeIsRelative, 305); + + y += yinc; + // smart spawning + AddLabel(25, y, 0x384, "SmartSpawn"); + AddCheck(5, y, 0xD2, 0xD3, defs.SmartSpawning, 310); + + // AllowGhost + AddLabel(125, y, 0x384, "AllowGhost"); + AddCheck(105, y, 0xD2, 0xD3, defs.AllowGhostTrig, 309); + + y += yinc; + // ExtTrig + AddLabel(25, y, 0x384, "ExtTrig"); + AddCheck(5, y, 0xD2, 0xD3, defs.ExternalTriggering, 308); + + // SpawnOnTrig + AddLabel(125, y, 0x384, "SpawnOnTrig"); + AddCheck(105, y, 0xD2, 0xD3, defs.SpawnOnTrigger, 311); + + y += yinc; + // AutoNumber + AddLabel(25, y, 0x384, "AutoNumber"); + AddCheck(5, y, 0xD2, 0xD3, defs.AutoNumber, 306); + AddImageTiled(105, y, 80, 19, 0xBBC); + AddTextEntry(105, y, 80, 19, 0, 125, defs.AutoNumberValue.ToString()); + + //y = 270; + yinc = 20; + y += yinc; + // Name + AddImageTiled(5, y, 95, 19, 0xBBC); + AddTextEntry(5, y, 85, 19, 0, 114, defs.SpawnerName); + AddLabel(105, y, 0x384, "SpawnerName"); + + y += yinc; + // speech trigger + AddLabel(105, y, 0x384, "SpeechTrigger"); + AddImageTiled(5, y, 95, 19, 0xBBC); + AddTextEntry(5, y, 85, 19, 0, 106, defs.SpeechTrigger); + + y += yinc; + // skill trigger + AddLabel(105, y, 0x384, "SkillTrigger"); + AddImageTiled(5, y, 95, 19, 0xBBC); + AddTextEntry(5, y, 85, 19, 0, 124, defs.SkillTrigger); + + y += yinc; + AddImageTiled(5, y, 95, 19, 0xBBC); + AddTextEntry(5, y, 85, 19, 0, 117, defs.TriggerOnCarried); + AddLabel(105, y, 0x384, "TrigOnCarried"); + + y += yinc; + AddImageTiled(5, y, 95, 19, 0xBBC); + AddTextEntry(5, y, 85, 19, 0, 118, defs.NoTriggerOnCarried); + AddLabel(105, y, 0x384, "NoTrigOnCarried"); + + y += yinc; + AddImageTiled(5, y, 95, 19, 0xBBC); + AddTextEntry(5, y, 85, 19, 0, 119, defs.ProximityMsg); + AddLabel(105, y, 0x384, "ProximityMsg"); + + y += yinc; + AddImageTiled(5, y, 95, 19, 0xBBC); + AddTextEntry(5, y, 85, 19, 0, 120, defs.PlayerTriggerProp); + AddLabel(105, y, 0x384, "PlayerTrigProp"); + + y += yinc; + AddImageTiled(5, y, 95, 19, 0xBBC); + AddTextEntry(5, y, 85, 19, 0, 122, defs.TriggerObjectProp); + AddLabel(105, y, 0x384, "TrigObjectProp"); + + //y = 429; + yinc = 23; + y += yinc; + // add the RestoreDefs button + AddButton(5, y, 0xFAE, 0xFAF, 117); + AddLabel(35, y, 0x384, "Restore Defs"); + + // add the RestoreDefs button + AddButton(125, y, 0xFAE, 0xFAF, 180); + AddLabel(155, y, 0x384, "Options"); + + y += yinc; + // add the SaveDefs button + AddButton(5, y, 0xFAE, 0xFAF, 115); + AddLabel(35, y, 0x384, "Save"); + + // add the LoadDefs button + AddButton(65, y, 0xFAE, 0xFAF, 116); + AddLabel(95, y, 0x384, "Load"); + + // add the DefsExt entry + AddImageTiled(127, y, 68, 21, 0xBBC); + AddTextEntry(129, y, 64, 21, 0, 115, defs.DefsExt); + + y += yinc; + // add the Add button + AddButton(5, y, 0xFAE, 0xFAF, 100); + AddLabel(35, y, 0x384, "Add"); + + // add the Goto button + AddButton(64, y, 0xFAE, 0xFAF, 1000); + AddLabel(94, y, 0x384, "Goto"); + + // add the Delete button + AddButton(125, y, 0xFAE, 0xFAF, 156); + AddLabel(155, y, 0x384, "Del"); + + // add the Edit button + // add the Find button + + // add gump extension button + if (defs.ShowExtension) + { + AddButton(480, y + 5, 0x15E3, 0x15E7, 200); + } + else + { + AddButton(180, y + 5, 0x15E1, 0x15E5, 200); + } + + if (defs.ShowExtension) + { + AddLabel(300, 5, 0x384, "Spawn Entries"); + // display the clear all toggle + AddButton(475, 5, 0xD2, 0xD3, 3999); + // display the selection entries + for (int i = 0; i < MaxEntries; i++) + { + int xpos = i / MaxEntriesPerColumn * 155; + int ypos = i % MaxEntriesPerColumn * 22 + 30; + + // background for search results area + AddImageTiled(xpos + 205, ypos, 116, 23, 0x52); + + // has this been selected for category info specification? + AddImageTiled(xpos + 206, ypos + 1, 114, 21, i == defs.CategorySelectionIndex ? 0x1436 : 0xBBC); + + bool sel = false; + if (defs.SelectionList != null && i < defs.SelectionList.Length) + { + sel = defs.SelectionList[i]; + } + + int texthue = 0; + if (sel) + { + texthue = 68; + } + + string namestr = null; + if (defs.NameList != null && i < defs.NameList.Length) + { + namestr = defs.NameList[i]; + } + + AddTextEntry(xpos + 208, ypos + 1, 110, 21, texthue, 1000 + i, namestr); + // display the selection button + AddButton(xpos + 320, ypos + 2, sel ? 0xD3 : 0xD2, sel ? 0xD2 : 0xD3, 4000 + i); + // display the info button + AddButton(xpos + 340, ypos + 2, 0x15E1, 0x15E5, 5000 + i); + } + } + } + + private void DoGoTo(Item x) + { + if (m_From == null || m_From.Deleted) + { + return; + } + + if (x == null || x.Deleted || x.Map == null) + { + return; + } + + Point3D itemloc; + + if (x.Parent != null) + { + if (x.RootParent is Container container) + { + itemloc = container.Location; + } + else + { + return; + } + } + else + { + itemloc = x.Location; + } + m_From.Location = itemloc; + m_From.Map = x.Map; + } + + private void DoShowProps(IEntity x) + { + if (m_From == null || m_From.Deleted) + { + return; + } + + if (x == null || x.Deleted || x.Map == null) + { + return; + } + + m_From.SendGump(new PropertiesGump(m_From, x)); + } + + private static void DoShowGump(Mobile from, Item x) + { + if (from == null || from.Deleted) + { + return; + } + + if (x == null || x.Deleted || x.Map == null || x.Map == Map.Internal) + { + return; + } + + x.OnDoubleClick(from); + } + + public static void Refresh(Mobile from) + { + Refresh(from, false); + } + + public static void Refresh(Mobile from, bool ignoreupdate) + { + if (from == null) + { + return; + } + + // read the text entries for default values + XmlSpawnerDefaults.DefaultEntry defs = null; + + Account acct = from.Account as Account; + if (acct != null) + { + defs = XmlSpawnerDefaults.GetDefaults(acct.ToString(), from.Name); + } + + if (defs == null) + { + return; + } + + int x = defs.AddGumpX; + int y = defs.AddGumpY; + if (defs.ShowExtension) + { + // shift the starting point + x = defs.AddGumpX - 140; + } + + defs.IgnoreUpdate = ignoreupdate; + + from.CloseGump(); + + defs.IgnoreUpdate = false; + from.SendGump(new XmlAddGump(from, defs.StartingLoc, defs.StartingMap, false, defs.ShowExtension, x, y)); + } + + private class PlaceSpawnerTarget : Target + { + readonly XmlSpawnerDefaults.DefaultEntry defs; + readonly NetState m_state; + + public PlaceSpawnerTarget(NetState state) : base(30, true, TargetFlags.None) + { + if (state?.Mobile == null) + { + return; + } + + // read the text entries for default values + defs = null; + + Account acct = state.Mobile.Account as Account; + if (acct != null) + { + defs = XmlSpawnerDefaults.GetDefaults(acct.ToString(), state.Mobile.Name); + } + + if (defs == null) + { + return; + } + + m_state = state; + } + + protected override void OnTarget(Mobile from, object targeted) + { + if (from == null) + { + return; + } + + // assign it a unique id + Guid SpawnId = Guid.NewGuid(); + // count the number of entries to be added for maxcount + int maxcount = 0; + for (int i = 0; i < MaxEntries; i++) + { + if (defs.SelectionList != null && i < defs.SelectionList.Length && defs.SelectionList[i] && + defs.NameList != null && i < defs.NameList.Length && defs.NameList[i] != null && defs.NameList[i].Length > 0) + { + maxcount++; + } + } + + // if autonumbering is enabled, name the spawner with the name+number + string sname = defs.SpawnerName; + if (defs.AutoNumber) + { + sname = $"{defs.SpawnerName}#{defs.AutoNumberValue}"; + } + + XmlSpawner spawner = new XmlSpawner(SpawnId, from.Location.X, from.Location.Y, 0, 0, sname, maxcount, + defs.MinDelay, defs.MaxDelay, defs.Duration, defs.ProximityRange, defs.ProximitySound, 1, + defs.Team, defs.HomeRange, defs.HomeRangeIsRelative, new XmlSpawner.SpawnObject[0], defs.RefractMin, defs.RefractMax, + defs.TODStart, defs.TODEnd, null, defs.TriggerObjectProp, defs.ProximityMsg, defs.TriggerOnCarried, defs.NoTriggerOnCarried, + defs.SpeechTrigger, null, null, defs.PlayerTriggerProp, defs.TriggerProbability, null, defs.Group, defs.TODMode, defs.KillReset, defs.ExternalTriggering, + defs.SequentialSpawn, null, defs.AllowGhostTrig, defs.AllowNPCTrig, defs.SpawnOnTrigger, null, defs.DespawnTime, defs.SkillTrigger, defs.SmartSpawning, null) + { + PlayerCreated = true + }; + + // if the object is a container, then place it in the container + if (targeted is Container container) + { + container.DropItem(spawner); + } + else + { + // place the spawner at the targeted location + IPoint3D p = targeted as IPoint3D; + if (p == null) + { + spawner.Delete(); + return; + } + if (p is Item item) + { + p = item.GetWorldTop(); + } + + spawner.MoveToWorld(new Point3D(p), from.Map); + } + + spawner.SpawnRange = defs.SpawnRange; + // add entries from the name list + for (int i = 0; i < MaxEntries; i++) + { + if (defs.SelectionList != null && i < defs.SelectionList.Length && defs.SelectionList[i] && + defs.NameList != null && i < defs.NameList.Length && defs.NameList[i] != null && defs.NameList[i].Length > 0) + { + spawner.AddSpawn = defs.NameList[i]; + } + } + + defs.LastSpawner = spawner; + + if (defs.AutoNumber) + // bump the autonumber + { + defs.AutoNumberValue++; + } + + //from.CloseGump(); + Refresh(m_state.Mobile, true); + + // open the spawner gump + DoShowGump(from, spawner); + } + } + + public override void OnResponse(NetState state, RelayInfo info) + { + if (info == null || state?.Mobile == null) + { + return; + } + + // read the text entries for default values + XmlSpawnerDefaults.DefaultEntry defaults = XmlSpawnerDefaults.GetDefaults(state.Account.ToString(), state.Mobile.Name); + if (defaults.IgnoreUpdate) + { + return; + } + + TextRelay tr = info.GetTextEntry(100); // mindelay + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.MinDelay = TimeSpan.FromMinutes(double.Parse(tr.Text)); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + tr = info.GetTextEntry(101); // maxdelay info + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.MaxDelay = TimeSpan.FromMinutes(double.Parse(tr.Text)); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(102); // min refractory + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.RefractMin = TimeSpan.FromMinutes(double.Parse(tr.Text)); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(103); // max refractory + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.RefractMax = TimeSpan.FromMinutes(double.Parse(tr.Text)); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(104); // TOD start + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.TODStart = TimeSpan.FromHours(double.Parse(tr.Text)); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(105); // TOD end + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.TODEnd = TimeSpan.FromHours(double.Parse(tr.Text)); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(106); // Speech trigger + if (tr != null) + { + string txt = tr.Text; + if (txt != null && txt.Length == 0) + { + txt = null; + } + + defaults.SpeechTrigger = txt; + } + + tr = info.GetTextEntry(107); // HomeRange + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.HomeRange = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(108); // SpawnRange + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.SpawnRange = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(109); // ProximityRange + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.ProximityRange = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(110); // Team + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.Team = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(111); // Duration + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.Duration = TimeSpan.FromMinutes(double.Parse(tr.Text)); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(112); // ProximitySound + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.ProximitySound = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(113); // Kill reset + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.KillReset = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(114); // Spawner name + if (tr != null) + { + defaults.SpawnerName = tr.Text; + } + + // DefsExt entry + tr = info.GetTextEntry(115); // save def str + if (tr != null) + { + defaults.DefsExt = tr.Text; + } + + tr = info.GetTextEntry(117); // trigger on carried + if (tr != null) + { + string txt = tr.Text; + if (txt != null && txt.Length == 0) + { + txt = null; + } + + defaults.TriggerOnCarried = txt; + } + + tr = info.GetTextEntry(118); // no trigger on carried + if (tr != null) + { + string txt = tr.Text; + if (txt != null && txt.Length == 0) + { + txt = null; + } + + defaults.NoTriggerOnCarried = txt; + } + + tr = info.GetTextEntry(119); // proximity message + if (tr != null) + { + string txt = tr.Text; + if (txt != null && txt.Length == 0) + { + txt = null; + } + + defaults.ProximityMsg = txt; + } + + tr = info.GetTextEntry(120); // player trig prop + if (tr != null) + { + string txt = tr.Text; + if (txt != null && txt.Length == 0) + { + txt = null; + } + + defaults.PlayerTriggerProp = txt; + } + + tr = info.GetTextEntry(121); // Trigger probability + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.TriggerProbability = double.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(122); // trig object prop + if (tr != null) + { + string txt = tr.Text; + if (txt != null && txt.Length == 0) + { + txt = null; + } + + defaults.TriggerObjectProp = txt; + } + + tr = info.GetTextEntry(123); // DespawnTime + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.DespawnTime = TimeSpan.FromHours(double.Parse(tr.Text)); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(124); // Skill trigger + if (tr != null) + { + string txt = tr.Text; + if (txt != null && txt.Length == 0) + { + txt = null; + } + + defaults.SkillTrigger = txt; + } + + tr = info.GetTextEntry(125); // AutoNumberValue + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defaults.AutoNumberValue = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + // fill the NameList from the text entries + if (defaults.ShowExtension) + { + for (int i = 0; i < MaxEntries; i++) + { + tr = info.GetTextEntry(1000 + i); + if (defaults.NameList != null && i < defaults.NameList.Length && tr != null) + { + defaults.NameList[i] = tr.Text; + } + } + } + + defaults.Group = info.IsSwitched(304); + defaults.HomeRangeIsRelative = info.IsSwitched(305); + defaults.AutoNumber = info.IsSwitched(306); + defaults.SequentialSpawn = info.IsSwitched(307) ? 0 : -1; + defaults.ExternalTriggering = info.IsSwitched(308); + defaults.AllowGhostTrig = info.IsSwitched(309); + defaults.SpawnOnTrigger = info.IsSwitched(311); + defaults.SmartSpawning = info.IsSwitched(310); + defaults.AllowNPCTrig = info.IsSwitched(312); + + switch (info.ButtonID) + { + case 0: // Close + { + return; + } + case 100: // Add spawner + { + state.Mobile.Target = new PlaceSpawnerTarget(state); + + break; + } + case 115: // SaveDefs + { + string filename; + if (!string.IsNullOrEmpty(defaults.DefsExt)) + { + filename = $"{defaults.AccountName}-{defaults.PlayerName}-{defaults.DefsExt}"; + } + else + { + filename = $"{defaults.AccountName}-{defaults.PlayerName}"; + } + DoSaveDefs(state.Mobile, filename); + break; + } + case 116: // LoadDefs + { + string filename; + if (!string.IsNullOrEmpty(defaults.DefsExt)) + { + filename = $"{defaults.AccountName}-{defaults.PlayerName}-{defaults.DefsExt}"; + } + else + { + filename = $"{defaults.AccountName}-{defaults.PlayerName}"; + } + DoLoadDefs(state.Mobile, filename); + break; + } + case 117: // Restore Defaults + { + state.Mobile.SendMessage("Restoring defaults"); + XmlSpawnerDefaults.RestoreDefs(defaults); + break; + } + case 155: // Return the player to the starting loc + { + m_From.Location = defaults.StartingLoc; + m_From.Map = defaults.StartingMap; + break; + } + case 156: // Delete last spawner + { + if (defaults.LastSpawner == null || defaults.LastSpawner.Deleted) + { + break; + } + + Refresh(state.Mobile); + state.Mobile.SendGump(new XmlAddConfirmDeleteGump(defaults.LastSpawner)); + return; + } + case 157: // Reset last spawner + { + if (defaults.LastSpawner != null && !defaults.LastSpawner.Deleted) + { + defaults.LastSpawner.DoReset = true; + } + + break; + } + case 158: // Respawn last spawner + { + if (defaults.LastSpawner != null && !defaults.LastSpawner.Deleted) + { + defaults.LastSpawner.DoRespawn = true; + } + + break; + } + case 180: // Set Options + { + Refresh(state.Mobile); + state.Mobile.SendGump(new XmlAddOptionsGump(state.Mobile)); + return; + } + case 200: // gump extension + { + defaults.ShowExtension = !defaults.ShowExtension; + break; + } + case 306: // TOD mode + { + defaults.TODMode = defaults.TODMode == XmlSpawner.TODModeType.Realtime ? XmlSpawner.TODModeType.Gametime : XmlSpawner.TODModeType.Realtime; + break; + } + case 1000: // GoTo + { + // then go to it + DoGoTo(defaults.LastSpawner); + break; + } + case 1001: // Show Gump + { + Refresh(state.Mobile); + DoShowGump(state.Mobile, defaults.LastSpawner); + break; + } + case 1002: // Show Props + { + Refresh(state.Mobile); + DoShowProps(defaults.LastSpawner); + break; + } + case 3999: // clear selections + { + // clear the selections + if (defaults.SelectionList != null) + { + Array.Clear(defaults.SelectionList, 0, defaults.SelectionList.Length); + } + + break; + } + case 9998: // refresh the gump + { + break; + } + default: + { + if (info.ButtonID >= 4000 && info.ButtonID < 4000 + MaxEntries) + { + int i = info.ButtonID - 4000; + if (defaults.SelectionList != null && i >= 0 && i < defaults.SelectionList.Length) + { + defaults.SelectionList[i] = !defaults.SelectionList[i]; + } + } + if (info.ButtonID >= 5000 && info.ButtonID < 5000 + MaxEntries) + { + int i = info.ButtonID - 5000; + + defaults.CategorySelectionIndex = i; + XmlAddGump newg = new XmlAddGump(state.Mobile, defaults.StartingLoc, defaults.StartingMap, false, defaults.ShowExtension, 0, 0); + + state.Mobile.SendGump(newg); + + if (defaults.NameList[i] == null || defaults.NameList[i].Length == 0) + { + // if no string has been entered then just use the full categorized add gump + state.Mobile.CloseGump(); + state.Mobile.SendGump(new XmlCategorizedAddGump(state.Mobile, defaults.CurrentCategory, defaults.CurrentCategoryPage, i, newg)); + } + else + { + // use the XmlPartialCategorizedAddGump + state.Mobile.CloseGump(); + + //Type [] types = (Type[])XmlPartialCategorizedAddGump.Match(defs.NameList[i]).ToArray(typeof(Type)); + ArrayList types = XmlPartialCategorizedAddGump.Match(defaults.NameList[i]); + state.Mobile.SendGump(new XmlPartialCategorizedAddGump(state.Mobile, defaults.NameList[i], 0, types, true, i, newg)); + } + + return; + } + + break; + } + } + + Refresh(state.Mobile); + } + + private class XmlAddOptionsGump : Gump + { + public XmlAddOptionsGump(Mobile from) : base(0, 0) + { + // read the text entries for default values + Account acct = from.Account as Account; + XmlSpawnerDefaults.DefaultEntry defs = null; + + if (acct != null) + { + defs = XmlSpawnerDefaults.GetDefaults(acct.ToString(), from.Name); + } + + if (defs == null) + { + return; + } + + Closable = true; + Draggable = true; + AddPage(0); + AddBackground(0, 0, 300, 130, 5054); + + AddLabel(20, 5, 0, "Options"); + // add the AddGumpX/Y entries + AddImageTiled(5, 30, 40, 21, 0xBBC); + AddTextEntry(5, 30, 40, 21, 0, 100, defs.AddGumpX.ToString()); + AddLabel(45, 30, 0x384, "AddGumpX"); + + AddImageTiled(135, 30, 40, 21, 0xBBC); + AddTextEntry(135, 30, 40, 21, 0, 101, defs.AddGumpY.ToString()); + AddLabel(175, 30, 0x384, "AddGumpY"); + + // add the SpawnerGumpX/Y entries + AddImageTiled(5, 55, 40, 21, 0xBBC); + AddTextEntry(5, 55, 40, 21, 0, 102, defs.SpawnerGumpX.ToString()); + AddLabel(45, 55, 0x384, "SpawnerGumpX"); + + AddImageTiled(135, 55, 40, 21, 0xBBC); + AddTextEntry(135, 55, 40, 21, 0, 103, defs.SpawnerGumpY.ToString()); + AddLabel(175, 55, 0x384, "SpawnerGumpY"); + + // add the FindGumpX/Y entries + AddImageTiled(5, 80, 40, 21, 0xBBC); + AddTextEntry(5, 80, 40, 21, 0, 104, defs.FindGumpX.ToString()); + AddLabel(45, 80, 0x384, "FindGumpX"); + + AddImageTiled(135, 80, 40, 21, 0xBBC); + AddTextEntry(135, 80, 40, 21, 0, 105, defs.FindGumpY.ToString()); + AddLabel(175, 80, 0x384, "FindGumpY"); + } + + public override void OnResponse(NetState state, RelayInfo info) + { + if (info == null || state?.Mobile == null) + { + return; + } + + // read the text entries for default values + XmlSpawnerDefaults.DefaultEntry defs = XmlSpawnerDefaults.GetDefaults(state.Account.ToString(), state.Mobile.Name); + if (defs == null) + { + return; + } + + TextRelay tr = info.GetTextEntry(100); // AddGumpX + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defs.AddGumpX = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + tr = info.GetTextEntry(101); // AddGumpY info + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defs.AddGumpY = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + tr = info.GetTextEntry(102); // SpawnerGumpX info + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defs.SpawnerGumpX = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + tr = info.GetTextEntry(103); // SpawnerGumpY info + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defs.SpawnerGumpY = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + tr = info.GetTextEntry(104); // FindGumpX info + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defs.FindGumpX = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + tr = info.GetTextEntry(105); // FindGumpY info + if (tr?.Text != null && tr.Text.Length > 0) + { + try { defs.FindGumpY = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + } + } + + private class XmlAddConfirmDeleteGump : Gump + { + private readonly XmlSpawner LastSpawner; + + public XmlAddConfirmDeleteGump(XmlSpawner lastSpawner) : base(0, 0) + { + LastSpawner = lastSpawner; + Closable = false; + Draggable = true; + AddPage(0); + AddBackground(10, 200, 200, 130, 5054); + + AddLabel(20, 225, 33, "Delete Last Spawner?"); + AddRadio(35, 255, 9721, 0x86A, false, 1); // accept/yes radio + AddRadio(135, 255, 9721, 0x86A, true, 2); // decline/no radio + AddHtmlLocalized(72, 255, 200, 30, 1049016, 0x7fff); // Yes + AddHtmlLocalized(172, 255, 200, 30, 1049017, 0x7fff); // No + AddButton(80, 289, 2130, 2129, 3); // Okay button + } + + public override void OnResponse(NetState state, RelayInfo info) + { + if (info == null || state?.Mobile == null) + { + return; + } + + int radiostate = -1; + if (info.Switches.Length > 0) + { + radiostate = info.Switches[0]; + } + switch (info.ButtonID) + { + default: + { + if (radiostate == 1 && LastSpawner != null && !LastSpawner.Deleted) + { // accept + // delete it + LastSpawner.Delete(); + } + break; + } + } + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlCategorizedAddGump.cs b/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlCategorizedAddGump.cs new file mode 100644 index 000000000..f8077e55a --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlCategorizedAddGump.cs @@ -0,0 +1,477 @@ +using Server.Mobiles; +using Server.Network; +using System; +using System.Collections; +using System.IO; +using System.Xml; + +namespace Server.Gumps; + +public abstract class XmlAddCAGNode +{ + public abstract string Caption { get; } + public abstract void OnClick(Mobile from, int page, int index, Gump gump); +} + +public class XmlAddCAGObject : XmlAddCAGNode +{ + private readonly Type m_Type; + private readonly XmlAddCAGCategory m_Parent; + + public int ItemID { get; } + + public override string Caption => m_Type == null ? "bad type" : m_Type.Name; + + public override void OnClick(Mobile from, int page, int index, Gump gump) + { + if (m_Type == null) + { + from.SendMessage("That is an invalid type name."); + } + else + { + if (gump is XmlAddGump xmladdgump) + { + //Commands.Handle(from, String.Format("{0}Add {1}", Commands.CommandPrefix, m_Type.Name)); + if (xmladdgump.defs?.NameList != null && index >= 0 && index < xmladdgump.defs.NameList.Length) + { + xmladdgump.defs.NameList[index] = m_Type.Name; + XmlAddGump.Refresh(from, true); + } + from.SendGump(new XmlCategorizedAddGump(from, m_Parent, page, index, xmladdgump)); + } + else if (gump is XmlSpawnerGump spawnerGump) + { + XmlSpawner m_Spawner = spawnerGump.m_Spawner; + + if (m_Spawner != null) + { + XmlSpawnerGump xg = m_Spawner.SpawnerGump; + + if (xg != null) + { + xg.Rentry = new XmlSpawnerGump.ReplacementEntry + { + Typename = m_Type.Name, + Index = index, + Color = 0x1436 + }; + + Timer.DelayCall(TimeSpan.Zero, XmlSpawnerGump.RefreshSpawnerGumps, from); + } + } + } + } + } + + public XmlAddCAGObject(XmlAddCAGCategory parent, XmlReader xml) + { + m_Parent = parent; + + if (xml.MoveToAttribute("type")) + { + m_Type = AssemblyHandler.FindTypeByFullName(xml.Value, false); + } + + if (xml.MoveToAttribute("gfx")) + { + ItemID = XmlConvert.ToInt32(xml.Value); + } + + if (xml.MoveToAttribute("hue")) + { + XmlConvert.ToInt32(xml.Value); + } + } +} + +public class XmlAddCAGCategory : XmlAddCAGNode +{ + private readonly string m_Title; + + public XmlAddCAGNode[] Nodes { get; } + + public XmlAddCAGCategory Parent { get; } + + public override string Caption => m_Title; + + public override void OnClick(Mobile from, int page, int index, Gump gump) + { + from.SendGump(new XmlCategorizedAddGump(from, this, 0, index, gump)); + } + + private XmlAddCAGCategory() + { + m_Title = "no data"; + Nodes = new XmlAddCAGNode[0]; + } + + public XmlAddCAGCategory(XmlAddCAGCategory parent, XmlReader xml) + { + Parent = parent; + + if (xml.MoveToAttribute("title")) + { + m_Title = xml.Value == "Add Menu" ? "XmlAdd Menu" : xml.Value; + } + else + { + m_Title = "empty"; + } + + if (m_Title == "Docked") + { + m_Title = "Docked 2"; + } + + if (xml.IsEmptyElement) + { + Nodes = new XmlAddCAGNode[0]; + } + else + { + ArrayList nodes = new ArrayList(); + + try + { + while (xml.Read() && xml.NodeType != XmlNodeType.EndElement) + { + + if (xml.NodeType == XmlNodeType.Element && xml.Name == "object") + { + nodes.Add(new XmlAddCAGObject(this, xml)); + } + else if (xml.NodeType == XmlNodeType.Element && xml.Name == "category") + { + if (!xml.IsEmptyElement) + { + nodes.Add(new XmlAddCAGCategory(this, xml)); + } + } + else + { + xml.Skip(); + } + } + } + catch (Exception ex) + { + Console.WriteLine("XmlCategorizedAddGump: Corrupted Data/objects.xml file detected. Not all XmlCAG objects loaded. {0}", ex); + } + + Nodes = (XmlAddCAGNode[])nodes.ToArray(typeof(XmlAddCAGNode)); + } + } + + private static XmlAddCAGCategory m_Root; + public static XmlAddCAGCategory Root => m_Root ?? (m_Root = Load("Data/objects.xml")); + + public static XmlAddCAGCategory Load(string path) + { + if (File.Exists(path)) + { + XmlTextReader xml = new XmlTextReader(path) + { + WhitespaceHandling = WhitespaceHandling.None + }; + + while (xml.Read()) + { + if (xml.Name == "category" && xml.NodeType == XmlNodeType.Element) + { + XmlAddCAGCategory cat = new XmlAddCAGCategory(null, xml); + + xml.Close(); + + return cat; + } + } + } + + return new XmlAddCAGCategory(); + } +} + +public class XmlCategorizedAddGump : Gump +{ + public static bool OldStyle = PropsConfig.OldStyle; + + public static readonly int GumpOffsetX = PropsConfig.GumpOffsetX; + public static readonly int GumpOffsetY = PropsConfig.GumpOffsetY; + + public static readonly int TextHue = PropsConfig.TextHue; + public static readonly int TextOffsetX = PropsConfig.TextOffsetX; + + public static readonly int OffsetGumpID = PropsConfig.OffsetGumpID; + public static readonly int HeaderGumpID = PropsConfig.HeaderGumpID; + public static readonly int EntryGumpID = PropsConfig.EntryGumpID; + public static readonly int BackGumpID = PropsConfig.BackGumpID; + public static readonly int SetGumpID = PropsConfig.SetGumpID; + + public static readonly int SetWidth = PropsConfig.SetWidth; + public static readonly int SetOffsetX = PropsConfig.SetOffsetX, SetOffsetY = PropsConfig.SetOffsetY /*+ (((EntryHeight - 20) / 2) / 2)*/; + public static readonly int SetButtonID1 = PropsConfig.SetButtonID1; + public static readonly int SetButtonID2 = PropsConfig.SetButtonID2; + + public static readonly int PrevWidth = PropsConfig.PrevWidth; + public static readonly int PrevOffsetX = PropsConfig.PrevOffsetX, PrevOffsetY = PropsConfig.PrevOffsetY /*+ (((EntryHeight - 20) / 2) / 2)*/; + public static readonly int PrevButtonID1 = PropsConfig.PrevButtonID1; + public static readonly int PrevButtonID2 = PropsConfig.PrevButtonID2; + + public static readonly int NextWidth = PropsConfig.NextWidth; + public static readonly int NextOffsetX = PropsConfig.NextOffsetX, NextOffsetY = PropsConfig.NextOffsetY /*+ (((EntryHeight - 20) / 2) / 2)*/; + public static readonly int NextButtonID1 = PropsConfig.NextButtonID1; + public static readonly int NextButtonID2 = PropsConfig.NextButtonID2; + + public static readonly int OffsetSize = PropsConfig.OffsetSize; + + public static readonly int EntryHeight = 24; + public static readonly int BorderSize = PropsConfig.BorderSize; + + private static readonly bool PrevLabel = false, NextLabel = false; + + private static readonly int PrevLabelOffsetX = PrevWidth + 1; + private const int PrevLabelOffsetY = 0; + + private const int NextLabelOffsetX = -29; + private const int NextLabelOffsetY = 0; + + private const int EntryWidth = 180; + private const int EntryCount = 15; + + private static readonly int TotalWidth = OffsetSize + EntryWidth + OffsetSize + SetWidth + OffsetSize; + + private static readonly int BackWidth = BorderSize + TotalWidth + BorderSize; + private readonly Mobile m_Owner; + private readonly XmlAddCAGCategory m_Category; + private int m_Page; + + private readonly int m_Index; + private readonly Gump m_Gump; + + public XmlCategorizedAddGump(Mobile owner, int index, Gump gump) : this(owner, XmlAddCAGCategory.Root, 0, index, gump) + { + } + + public XmlCategorizedAddGump(Mobile owner, XmlAddCAGCategory category, int page, int index, Gump gump) : base(GumpOffsetX, GumpOffsetY) + { + if (category == null) + { + category = XmlAddCAGCategory.Root; + page = 0; + } + + owner.CloseGump(); + + m_Owner = owner; + m_Category = category; + + m_Index = index; + m_Gump = gump; + + if (gump is XmlAddGump xmladdgump) + { + if (xmladdgump.defs != null) + { + xmladdgump.defs.CurrentCategory = category; + xmladdgump.defs.CurrentCategoryPage = page; + } + } + + Initialize(page); + } + + public void Initialize(int page) + { + m_Page = page; + + XmlAddCAGNode[] nodes = m_Category.Nodes; + + int count = nodes.Length - page * EntryCount; + + if (count < 0) + { + count = 0; + } + else if (count > EntryCount) + { + count = EntryCount; + } + + int totalHeight = OffsetSize + (EntryHeight + OffsetSize) * (count + 1); + + AddPage(0); + + AddBackground(0, 0, BackWidth, BorderSize + totalHeight + BorderSize, BackGumpID); + AddImageTiled(BorderSize, BorderSize, TotalWidth - (OldStyle ? SetWidth + OffsetSize : 0), totalHeight, OffsetGumpID); + + int x = BorderSize + OffsetSize; + int y = BorderSize + OffsetSize; + + if (OldStyle) + { + AddImageTiled(x, y, TotalWidth - OffsetSize * 3 - SetWidth, EntryHeight, HeaderGumpID); + } + else + { + AddImageTiled(x, y, PrevWidth, EntryHeight, HeaderGumpID); + } + + if (m_Category.Parent != null) + { + AddButton(x + PrevOffsetX, y + PrevOffsetY, PrevButtonID1, PrevButtonID2, 1); + + if (PrevLabel) + { + AddLabel(x + PrevLabelOffsetX, y + PrevLabelOffsetY, TextHue, "Previous"); + } + } + + x += PrevWidth + OffsetSize; + + int emptyWidth = TotalWidth - PrevWidth * 2 - NextWidth - OffsetSize * 5 - (OldStyle ? SetWidth + OffsetSize : 0); + + if (!OldStyle) + { + AddImageTiled(x - (OldStyle ? OffsetSize : 0), y, emptyWidth + (OldStyle ? OffsetSize * 2 : 0), EntryHeight, EntryGumpID); + } + + AddHtml(x + TextOffsetX, y + (EntryHeight - 20) / 2, emptyWidth - TextOffsetX, EntryHeight, + $"
{m_Category.Caption}
"); + + x += emptyWidth + OffsetSize; + + if (OldStyle) + { + AddImageTiled(x, y, TotalWidth - OffsetSize * 3 - SetWidth, EntryHeight, HeaderGumpID); + } + else + { + AddImageTiled(x, y, PrevWidth, EntryHeight, HeaderGumpID); + } + + if (page > 0) + { + AddButton(x + PrevOffsetX, y + PrevOffsetY, PrevButtonID1, PrevButtonID2, 2); + + if (PrevLabel) + { + AddLabel(x + PrevLabelOffsetX, y + PrevLabelOffsetY, TextHue, "Previous"); + } + } + + x += PrevWidth + OffsetSize; + + if (!OldStyle) + { + AddImageTiled(x, y, NextWidth, EntryHeight, HeaderGumpID); + } + + if ((page + 1) * EntryCount < nodes.Length) + { + AddButton(x + NextOffsetX, y + NextOffsetY, NextButtonID1, NextButtonID2, 3, GumpButtonType.Reply, 1); + + if (NextLabel) + { + AddLabel(x + NextLabelOffsetX, y + NextLabelOffsetY, TextHue, "Next"); + } + } + + for (int i = 0, index = page * EntryCount; i < EntryCount && index < nodes.Length; ++i, ++index) + { + x = BorderSize + OffsetSize; + y += EntryHeight + OffsetSize; + + XmlAddCAGNode node = nodes[index]; + + AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID); + AddLabelCropped(x + TextOffsetX, y + (EntryHeight - 20) / 2, EntryWidth - TextOffsetX, EntryHeight, TextHue, node.Caption); + + x += EntryWidth + OffsetSize; + + if (SetGumpID != 0) + { + AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID); + } + + AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, i + 4); + + if (node is XmlAddCAGObject obj) + { + int itemID = obj.ItemID; + + Rectangle2D bounds = ItemBounds.Table[itemID]; + + if (itemID != 1 && bounds.Height < EntryHeight * 2) + { + if (bounds.Height < EntryHeight) + { + AddItem(x - OffsetSize - 22 - i % 2 * 44 - bounds.Width / 2 - bounds.X, y + EntryHeight / 2 - bounds.Height / 2 - bounds.Y, itemID); + } + else + { + AddItem(x - OffsetSize - 22 - i % 2 * 44 - bounds.Width / 2 - bounds.X, y + EntryHeight - 1 - bounds.Height - bounds.Y, itemID); + } + } + } + } + } + + public override void OnResponse(NetState state, RelayInfo info) + { + Mobile from = m_Owner; + + switch (info.ButtonID) + { + case 0: // Closed + { + return; + } + case 1: // Up + { + if (m_Category.Parent != null) + { + int index = Array.IndexOf(m_Category.Parent.Nodes, m_Category) / EntryCount; + + if (index < 0) + { + index = 0; + } + + from.SendGump(new XmlCategorizedAddGump(from, m_Category.Parent, index, m_Index, m_Gump)); + } + + break; + } + case 2: // Previous + { + if (m_Page > 0) + { + from.SendGump(new XmlCategorizedAddGump(from, m_Category, m_Page - 1, m_Index, m_Gump)); + } + + break; + } + case 3: // Next + { + if ((m_Page + 1) * EntryCount < m_Category.Nodes.Length) + { + from.SendGump(new XmlCategorizedAddGump(from, m_Category, m_Page + 1, m_Index, m_Gump)); + } + + break; + } + default: + { + int index = m_Page * EntryCount + (info.ButtonID - 4); + + if (index >= 0 && index < m_Category.Nodes.Length) + { + m_Category.Nodes[index].OnClick(from, m_Page, m_Index, m_Gump); + } + + break; + } + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlEdit.cs b/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlEdit.cs new file mode 100644 index 000000000..e61f7113c --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlEdit.cs @@ -0,0 +1,1420 @@ +using System; +using System.Collections; +using Server.Items; +using Server.Network; +using Server.Gumps; +using Server.Targeting; +using CPA = Server.CommandPropertyAttribute; +using Server.Mobiles; + +/* +** XmlEditNPC +** Version 1.00 +** updated 5/24/05 +** ArteGordon +** +** +*/ + +namespace Server.Engines.XmlSpawner2; + +public class XmlEditDialogGump : Gump +{ + private const int MaxEntries = 8; + private const int MaxEntriesPerPage = 8; + + private Mobile m_From; + private string Name; + private int Selected; + private int DisplayFrom; + private string SaveFilename; + private bool [] m_SelectionList; + private XmlDialog m_Dialog; + + private bool SelectAll; + + private ArrayList m_SearchList; + + public static void Initialize() + { + CommandSystem.Register("XmlEdit", AccessLevel.GameMaster, XmlEditDialog_OnCommand); + } + + [Usage("XmlEdit")] + [Description("Edits XmlDialog entries on an object")] + public static void XmlEditDialog_OnCommand(CommandEventArgs e) + { + if (e == null || e.Mobile == null) + { + return; + } + + // target an object with the xmldialog attachment + e.Mobile.Target = new EditDialogTarget(); + + + } + + private class EditDialogTarget : Target + { + + public EditDialogTarget() : base (30, true, TargetFlags.None) + { + + } + + protected override void OnTarget(Mobile from, object targeted) + { + if (from == null) + { + return; + } + + // does it have an xmldialog attachment? + XmlDialog xd = XmlAttach.FindAttachment(targeted, typeof(XmlDialog)) as XmlDialog; + + if (xd == null) + { + from.SendMessage("Target has no XmlDialog attachment"); + + // TODO: ask whether they would like to add one + from.SendGump(new XmlConfirmAddGump(from, targeted)); + + return; + } + + from.SendGump(new XmlEditDialogGump(from, true, xd, -1, 0, null, false, null, 0, 0)); + } + } + + public class XmlConfirmAddGump : Gump + { + private Mobile m_From; + private object m_Targeted; + + public XmlConfirmAddGump(Mobile from, object targeted) : base (0, 0) + { + if (from == null || targeted == null) + { + return; + } + + m_Targeted = targeted; + m_From = from; + + Closable = false; + Draggable = true; + AddPage(0); + AddBackground(10, 200, 200, 130, 5054); + + AddLabel(20, 210, 68, "Add an XmlDialog to target?"); + + string name = null; + if (targeted is Item item) + { + name = item.Name; + } + else + if (targeted is Mobile mobile) + { + name = mobile.Name; + } + + if (name == null) + { + name = targeted.GetType().Name; + } + AddLabel(20, 230, 0, $"{name}"); + + AddRadio(35, 255, 9721, 9724, false, 1); // accept/yes radio + AddRadio(135, 255, 9721, 9724, true, 2); // decline/no radio + AddHtmlLocalized(72, 255, 200, 30, 1049016, 0x7fff); // Yes + AddHtmlLocalized(172, 255, 200, 30, 1049017, 0x7fff); // No + AddButton(80, 289, 2130, 2129, 3); // Okay button + + } + public override void OnResponse(NetState state, RelayInfo info) + { + if (info == null || state == null || state.Mobile == null) + { + return; + } + + int radiostate = -1; + if (info.Switches.Length > 0) + { + radiostate = info.Switches[0]; + } + switch (info.ButtonID) + { + + default: + { + if (radiostate == 1) + { // accept + + // add the attachment + XmlDialog xd = new XmlDialog(); + XmlAttach.AttachTo(state.Mobile, m_Targeted, xd); + + // open the editing gump + state.Mobile.SendGump(new XmlEditDialogGump(state.Mobile, true, xd, -1, 0, null, false, null, 0, 0)); + } + break; + } + } + } + } + + public const int MaxLabelLength = 200; + public string TruncateLabel(string s) + { + if (s == null || s.Length < MaxLabelLength) + { + return s; + } + + return s.Substring(0,MaxLabelLength); + } + + public XmlEditDialogGump(Mobile from, bool firststart, + XmlDialog dialog, int selected, int displayfrom, string savefilename, + bool selectall, bool [] selectionlist, int X, int Y) : base(X,Y) + { + + if (from == null || dialog == null) + { + return; + } + + m_Dialog = dialog; + m_From = from; + + m_SelectionList = selectionlist; + if (m_SelectionList == null) + { + m_SelectionList = new bool[MaxEntries]; + } + SaveFilename = savefilename; + + DisplayFrom = displayfrom; + Selected = selected; + + // fill the list with the XmlDialog entries + m_SearchList = dialog.SpeechEntries; + + // prepare the page + int height = 500; + + + AddPage(0); + + AddBackground(0, 0, 755, height, 5054); + AddAlphaRegion(0, 0, 755, height); + + // add the separators + AddImageTiled(10, 80, 735, 4, 0xBB9); + AddImageTiled(10, height -212, 735, 4, 0xBB9); + AddImageTiled(10, height -60, 735, 4, 0xBB9); + + // add the xmldialog properties + int y = 5; + int w = 140; + int x = 5; + int lw = 0; + // the dialog name + AddImageTiled(x, y, w, 21, 0x23F4); + // get the name of the object this is attached to + + if (m_Dialog.AttachedTo is Item item) + { + Name = item.Name; + } + else + if (m_Dialog.AttachedTo is Mobile mobile) + { + Name = mobile.Name; + } + if (Name == null && m_Dialog.AttachedTo != null) + { + Name = m_Dialog.AttachedTo.GetType().Name; + } + AddLabelCropped(x, y, w, 21, 0, Name); + + + x += w + lw + 20; + w = 40; + lw = 90; + // add the proximity range + AddLabel(x, y, 0x384, "ProximityRange"); + AddImageTiled(x+lw, y, w, 21, 0xBBC); + AddTextEntry(x+lw, y, w, 21, 0, 140, m_Dialog.ProximityRange.ToString()); + + x += w + lw + 20; + w = 100; + lw = 60; + // reset time + AddLabel(x, y, 0x384, "ResetTime"); + AddImageTiled(x+lw, y, w, 21, 0xBBC); + AddTextEntry(x+lw, y, w, 21, 0, 141, m_Dialog.ResetTime.ToString()); + + x += w + lw + 20; + w = 40; + lw = 65; + // speech pace + AddLabel(x, y, 0x384, "SpeechPace"); + AddImageTiled(x+lw, y, w, 21, 0xBBC); + AddTextEntry(x+lw, y, w, 21, 0, 142, m_Dialog.SpeechPace.ToString()); + + x += w + lw + 20; + w = 30; + lw = 55; + // allow ghost triggering + AddLabel(x, y, 0x384, "GhostTrig"); + AddCheck(x+lw, y, 0xD2, 0xD3, m_Dialog.AllowGhostTrig, 260); + + // add the triggeroncarried + y += 27; + w = 600; + x = 10; + lw = 100; + AddLabel(10, y, 0x384, "TrigOnCarried"); + AddImageTiled(x + lw, y, w, 21, 0xBBC); + AddTextEntry(x+lw, y, w, 21, 0, 150, TruncateLabel(m_Dialog.TriggerOnCarried)); + AddButton(720, y, 0xFAB, 0xFAD, 5005); + + // add the notriggeroncarried + y += 22; + w = 600; + x = 10; + lw = 100; + AddLabel(x, y, 0x384, "NoTrigOnCarried"); + AddImageTiled(x+lw, y, w, 21, 0xBBC); + AddTextEntry(x + lw, y, w, 21, 0, 151, TruncateLabel(m_Dialog.NoTriggerOnCarried)); + AddButton(720, y, 0xFAB, 0xFAD, 5006); + + y = 88; + // column labels + AddLabel(10, y, 0x384, "Edit"); + AddLabel(45, y, 0x384, "#"); + AddLabel(95, y, 0x384, "ID"); + AddLabel(145, y, 0x384, "Depends"); + AddLabel(195, y, 0x384, "Keywords"); + AddLabel(295, y, 0x384, "Text"); + AddLabel(540, y, 0x384, "Action"); + AddLabel(602, y, 0x384, "Condition"); + AddLabel(664, y, 0x384, "Gump"); + + // display the select-all-displayed toggle + AddButton(730, y, 0xD2, 0xD3, 3999); + + y -= 10; + for (int i = 0; i < MaxEntries; i++) + { + int index = i + DisplayFrom; + if (m_SearchList == null || index >= m_SearchList.Count) + { + break; + } + + int page = i/MaxEntriesPerPage; + if (i%MaxEntriesPerPage == 0) + { + AddPage(page+1); + // add highlighted page button + //AddImageTiled(235+page*25, 448, 25, 25, 0xBBC); + //AddImage(238+page*25, 450, 0x8B1+page); + } + + // background for search results area + AddImageTiled(235, y + 22 * (i%MaxEntriesPerPage) + 30, 386, 23, 0x52); + AddImageTiled(236, y + 22 * (i%MaxEntriesPerPage) + 31, 384, 21, 0xBBC); + + + XmlDialog.SpeechEntry s = (XmlDialog.SpeechEntry)m_SearchList[index]; + + if (s == null) + { + continue; + } + + int texthue = 0; + + bool sel=false; + + if (m_SelectionList != null && i < m_SelectionList.Length) + { + sel = m_SelectionList[i]; + } + + // entries with the selection box checked are highlighted in red + if (sel) + { + texthue = 33; + } + + // the selected entry is highlighted in green + if (i == Selected) + { + texthue = 68; + } + + x = 10; + w = 35; + // add the Edit button for each entry + AddButton(10, y + 22 * (i%MaxEntriesPerPage) + 30, 0xFAE, 0xFAF, 1000+i); + + x += w; + w = 50; + // display the entry number + AddImageTiled(x, y + 22 * (i%MaxEntriesPerPage) + 31, w, 21, 0xBBC); + AddLabel(x, y + 22 * (i%MaxEntriesPerPage) + 31, texthue, s.EntryNumber.ToString()); + + x += w; + w = 50; + // display the entry ID + AddImageTiled(x, y + 22 * (i%MaxEntriesPerPage) + 31, w, 21, 0x23F4); + AddLabel(x, y + 22 * (i%MaxEntriesPerPage) + 31, texthue, s.ID.ToString()); + + x += w; + w = 50; + // display the entry dependson + AddImageTiled(x, y + 22 * (i%MaxEntriesPerPage) + 31, w, 21, 0xBBC ); + AddLabel(x, y + 22 * (i%MaxEntriesPerPage) + 31, texthue, s.DependsOn); + + x += w; + w = 100; + // display the entry keywords + AddImageTiled(x, y + 22 * (i%MaxEntriesPerPage) + 31, w, 21, 0x23F4); + AddLabelCropped(x, y + 22 * (i%MaxEntriesPerPage) + 31, w-5, 21, texthue, TruncateLabel(s.Keywords)); + + x += w; + w = 245; + // display the entry text + AddImageTiled(x, y + 22 * (i%MaxEntriesPerPage) + 31, w, 21, 0xBBC); + AddLabelCropped(x, y + 22 * (i%MaxEntriesPerPage) + 31, w-5, 21, texthue, TruncateLabel(s.Text)); + + x += w; + w = 62; + // display the action text + AddImageTiled(x, y + 22 * (i%MaxEntriesPerPage) + 31, w, 21, 0x23F4); + AddLabelCropped(x, y + 22 * (i%MaxEntriesPerPage) + 31, w-5, 21, texthue, TruncateLabel(s.Action)); + + x += w; + w = 62; + // display the condition text + AddImageTiled(x, y + 22 * (i%MaxEntriesPerPage) + 31, w, 21, 0xBBC); + AddLabelCropped(x, y + 22 * (i%MaxEntriesPerPage) + 31, w-5, 21, texthue, TruncateLabel(s.Condition)); + + x += w; + w = 62; + // display the gump text + AddImageTiled(x, y + 22 * (i%MaxEntriesPerPage) + 31, w, 21, 0x23F4); + AddLabelCropped(x, y + 22 * (i%MaxEntriesPerPage) + 31, w-5, 21, texthue, TruncateLabel(s.Gump)); + + // display the selection button + AddButton(730, y + 22 * (i%MaxEntriesPerPage) + 32, sel? 0xD3:0xD2, sel? 0xD2:0xD3, 4000+i); + + } + + + // display the selected entry information for editing + XmlDialog.SpeechEntry sentry = null; + if (Selected >= 0 && Selected + DisplayFrom >= 0 && Selected + DisplayFrom < m_SearchList.Count) + { + sentry = (XmlDialog.SpeechEntry)m_SearchList[Selected+ DisplayFrom]; + } + + if (sentry != null) + { + + y = height - 200; + + // add the entry parameters + lw = 15; + w = 40; + x = 10; + int spacing = 11; + + // entry number + AddLabel(x, y, 0x384, "#"); + AddImageTiled(x+lw, y, w, 21, 0xBBC); + AddTextEntry(x+lw, y, w, 21, 0, 200, sentry.EntryNumber.ToString()); + + x += w + lw + spacing; + w = 40; + lw = 17; + // ID number + AddLabel(x, y, 0x384, "ID"); + AddImageTiled(x+lw, y, w, 21, 0xBBC); + AddTextEntry(x+lw, y, w, 21, 0, 201, sentry.ID.ToString()); + + x += w + lw + spacing; + w = 40; + lw = 65; + // depends on + AddLabel(x, y, 0x384, "DependsOn"); + AddImageTiled(x+lw, y, w, 21, 0xBBC); + AddTextEntry(x+lw, y, w, 21, 0, 202, sentry.DependsOn); + + x += w + lw + spacing; + w = 35; + lw = 57; + // prepause + AddLabel(x, y, 0x384, "PrePause"); + AddImageTiled(x+lw, y, w, 21, 0xBBC); + AddTextEntry(x+lw, y, w, 21, 0, 203, sentry.PrePause.ToString()); + + x += w + lw + spacing; + w = 35; + lw = 37; + // pause + AddLabel(x, y, 0x384, "Pause"); + AddImageTiled(x+lw, y, w, 21, 0xBBC); + AddTextEntry(x+lw, y, w, 21, 0, 204, sentry.Pause.ToString()); + + x += w + lw + spacing; + w = 37; + lw = 26; + // speech hue + AddLabel(x, y, 0x384, "Hue"); + AddImageTiled(x+lw, y, w, 21, 0xBBC); + AddTextEntry(x+lw, y, w, 21, 0, 205, sentry.SpeechHue.ToString()); + + x += w + lw + spacing; + w = 20; + lw = 52; + // lock conversation + AddLabel(x, y, 0x384, "IgnoreCar"); + AddCheck(x + lw, y, 0xD2, 0xD3, sentry.IgnoreCarried, 252); + + x += w + lw + spacing; + w = 20; + lw = 42; + // lock conversation + AddLabel(x, y, 0x384, "LockOn"); + AddCheck(x+lw, y, 0xD2, 0xD3, sentry.LockConversation, 250); + + x += w + lw + spacing; + w = 20; + lw = 54; + // npctrigger + AddLabel(x, y, 0x384, "AllowNPC"); + AddCheck(x+lw, y, 0xD2, 0xD3, sentry.AllowNPCTrigger, 251); + + + w = 650; + x = 70; + + y += 27; + // add the keyword entry + AddLabel(10, y, 0x384, "Keywords"); + AddImageTiled(x, y, w, 21, 0xBBC); + AddTextEntry(x+1, y, w, 21, 0, 101, sentry.Keywords); + AddButton(720, y, 0xFAB, 0xFAD, 5001); + + y += 22; + // add the text entry + AddLabel(10, y, 0x384, "Text"); + AddImageTiled(x, y, w, 21, 0xBBC); + AddTextEntry(x+1, y, w, 21, 0, 100, sentry.Text); + AddButton(720, y, 0xFAB, 0xFAD, 5000); + + + y += 22; + // add the condition string entry + AddLabel(10, y, 0x384, "Condition"); + AddImageTiled(x, y, w, 21, 0xBBC); + AddTextEntry(x+1, y, w, 21, 0, 102, sentry.Condition); + AddButton(720, y, 0xFAB, 0xFAD, 5002); + + y += 22; + // add the action string entry + AddLabel(10, y, 0x384, "Action"); + AddImageTiled(x, y, w, 21, 0xBBC); + AddTextEntry(x+1, y, w, 21, 0, 103, sentry.Action); + AddButton(720, y, 0xFAB, 0xFAD, 5003); + + y += 22; + // add the gump string entry + AddLabel(10, y, 0x384, "Gump"); + AddImageTiled(x, y, w, 21, 0xBBC); + AddTextEntry(x+1, y, w, 21, 0, 104, sentry.Gump); + AddButton(720, y, 0xFAB, 0xFAD, 5004); + } + + y = height - 50; + + AddLabel(10, y, 0x384, "Config:"); + AddImageTiled(50, y , 120, 19, 0x23F4); + AddLabel(50, y, 0, m_Dialog.ConfigFile); + + if (from.AccessLevel >= XmlSpawner.DiskAccessLevel) + { + + // add the save entry + AddButton(185, y , 0xFA8, 0xFAA, 159); + AddLabel(218, y , 0x384, "Save to file:"); + AddImageTiled(300, y , 180, 19, 0xBBC); + AddTextEntry(300, y, 180, 19, 0, 300, SaveFilename); + } + + // display the item list + if (m_SearchList != null) + { + AddLabel(495, y, 68, $"{m_SearchList.Count} Entries"); + int last = DisplayFrom + MaxEntries < m_SearchList.Count ? DisplayFrom + MaxEntries : m_SearchList.Count; + if (last > 0) + { + AddLabel(595, y, 68, $"Displaying {DisplayFrom}-{last - 1}"); + } + } + + y = height - 25; + + // add run status display + if (m_Dialog.Running) + { + AddButton(10, y-5, 0x2A4E, 0x2A3A, 100); + AddLabel(43, y, 0x384, "On"); + } + else + { + AddButton(10, y-5, 0x2A62, 0x2A3A, 100); + AddLabel(43, y, 0x384, "Off"); + } + + // add the Refresh/Sort button + AddButton(80, y, 0xFAB, 0xFAD, 700); + AddLabel(113, y, 0x384, "Refresh"); + + // add the Add button + AddButton(185, y, 0xFAB, 0xFAD, 155); + AddLabel(218, y, 0x384, "Add"); + + // add the Delete button + AddButton(255, y, 0xFB1, 0xFB3, 156); + AddLabel(283, y, 0x384, "Delete"); + + // add the page buttons + for(int i = 0;i 0) + { + + m_SearchList.Sort(new ListSorter(false)); + + } + } + + private class ListSorter : IComparer + { + private bool Dsort; + public ListSorter(bool descend) => Dsort = descend; + + public int Compare(object x, object y) + { + int xn = 0; + int yn = 0; + + + xn = ((XmlDialog.SpeechEntry)x).EntryNumber; + + yn = ((XmlDialog.SpeechEntry)y).EntryNumber; + + + if (Dsort) + { + return yn - xn; + } + + return xn- yn; + } + } + + + + private void SaveList(Mobile from, string filename) + { + if (m_SearchList == null || m_SelectionList == null) + { + return; + } + + string dirname; + if (System.IO.Directory.Exists(XmlDialog.DefsDir) && filename != null && !filename.StartsWith("/") && !filename.StartsWith("\\")) + { + // put it in the defaults directory if it exists + dirname = $"{XmlDialog.DefsDir}/{filename}"; + } + else + { + // otherwise just put it in the main installation dir + dirname = filename; + } + + // save it to the file + } + + private XmlEditDialogGump Refresh(NetState state) + { + XmlEditDialogGump g = new XmlEditDialogGump(m_From, false, m_Dialog, Selected, + DisplayFrom, SaveFilename, SelectAll, m_SelectionList, X, Y); + state.Mobile.SendGump(g); + return g; + } + + public static void ProcessXmlEditBookEntry(Mobile from, object[] args, string text) + { + + if (from == null || args == null || args.Length < 6) + { + return; + } + + XmlDialog dialog = (XmlDialog)args[0]; + XmlDialog.SpeechEntry entry = (XmlDialog.SpeechEntry)args[1]; + int textid = (int)args[2]; + int selected = (int)args[3]; + int displayfrom = (int)args[4]; + string savefile = (string)args[5]; + + // place the book text into the entry by type + switch (textid) + { + case 0: // text + { + if (entry != null) + { + entry.Text = text; + } + + break; + } + case 1: // keywords + { + if (entry != null) + { + entry.Keywords = text; + } + + break; + } + case 2: // condition + { + if (entry != null) + { + entry.Condition = text; + } + + break; + } + case 3: // action + { + if (entry != null) + { + entry.Action = text; + } + + break; + } + case 4: // gump + { + if (entry != null) + { + entry.Gump = text; + } + + break; + } + case 5: // trigoncarried + { + if (dialog != null) + { + dialog.TriggerOnCarried = text; + } + + break; + } + case 6: // notrigoncarried + { + if (dialog != null) + { + dialog.NoTriggerOnCarried = text; + } + + break; + } + } + + + from.CloseGump(); + + //from.SendGump(new XmlEditDialogGump(from, false, m_Dialog, selected, displayfrom, savefilename, false, null, X, Y)); + from.SendGump(new XmlEditDialogGump(from, true, dialog, selected, displayfrom, savefile, false, null, 0, 0)); + } + + + public override void OnResponse(NetState state, RelayInfo info) + { + if (info == null || state == null || state.Mobile == null || m_Dialog == null) + { + return; + } + + int radiostate = -1; + if (info.Switches.Length > 0) + { + radiostate = info.Switches[0]; + } + + + + TextRelay tr = info.GetTextEntry(400); // displayfrom info + try + { + DisplayFrom = int.Parse(tr.Text); + } + catch{} + + + tr = info.GetTextEntry(300); // savefilename info + if (tr != null) + { + SaveFilename = tr.Text; + } + + if (m_Dialog != null) + { + tr = info.GetTextEntry(140); // proximity range + if (tr != null) + { + try + { + m_Dialog.ProximityRange = int.Parse(tr.Text); + } + catch{} + } + tr = info.GetTextEntry(141); // reset time + if (tr != null) + { + try + { + m_Dialog.ResetTime = TimeSpan.Parse(tr.Text); + } + catch{} + } + tr = info.GetTextEntry(142); // speech pace + if (tr != null) + { + try + { + m_Dialog.SpeechPace = int.Parse(tr.Text); + } + catch{} + } + + tr = info.GetTextEntry(150); // trig on carried + if (tr != null && (m_Dialog.TriggerOnCarried == null || m_Dialog.TriggerOnCarried.Length < 230)) + { + if (tr.Text != null && tr.Text.Trim().Length > 0) + { + m_Dialog.TriggerOnCarried = tr.Text; + } + else + { + m_Dialog.TriggerOnCarried = null; + } + } + + tr = info.GetTextEntry(151); // notrig on carried + if (tr != null && (m_Dialog.NoTriggerOnCarried == null || m_Dialog.NoTriggerOnCarried.Length < 230)) + { + if (tr.Text != null && tr.Text.Trim().Length > 0) + { + m_Dialog.NoTriggerOnCarried = tr.Text; + } + else + { + m_Dialog.NoTriggerOnCarried = null; + } + } + + m_Dialog.AllowGhostTrig = info.IsSwitched(260); // allow ghost triggering + } + + if (m_SearchList != null && Selected >= 0 && Selected + DisplayFrom >= 0 && Selected + DisplayFrom < m_SearchList.Count) + { + // entry information + XmlDialog.SpeechEntry entry = (XmlDialog.SpeechEntry)m_SearchList[Selected + DisplayFrom]; + + tr = info.GetTextEntry(200); // entry number + if (tr != null) + { + try + { + entry.EntryNumber = int.Parse(tr.Text); + } + catch {} + } + + tr = info.GetTextEntry(201); // entry id + if (tr != null) + { + try + { + entry.ID = int.Parse(tr.Text); + } + catch {} + } + + tr = info.GetTextEntry(202); // depends on + if (tr != null) + { + try + { + entry.DependsOn = tr.Text; + } + catch {} + } + + tr = info.GetTextEntry(203); // prepause + if (tr != null) + { + try + { + entry.PrePause = int.Parse(tr.Text); + } + catch {} + } + + tr = info.GetTextEntry(204); // pause + if (tr != null) + { + try + { + entry.Pause = int.Parse(tr.Text); + } + catch {} + } + + tr = info.GetTextEntry(205); // hue + if (tr != null) + { + try + { + entry.SpeechHue = int.Parse(tr.Text); + } + catch {} + } + + tr = info.GetTextEntry(101); // keywords + if (tr != null && (entry.Keywords == null || entry.Keywords.Length < 230)) + { + if (tr.Text != null && tr.Text.Trim().Length > 0) + { + entry.Keywords = tr.Text; + } + else + { + entry.Keywords = null; + } + + } + + tr = info.GetTextEntry(100); // text + if (tr != null && (entry.Text == null || entry.Text.Length < 230)) + { + if (tr.Text != null && tr.Text.Trim().Length > 0) + { + entry.Text = tr.Text; + } + else + { + entry.Text = null; + } + } + + tr = info.GetTextEntry(102); // condition + if (tr != null && (entry.Condition == null || entry.Condition.Length < 230)) + { + if (tr.Text != null && tr.Text.Trim().Length > 0) + { + entry.Condition = tr.Text; + } + else + { + entry.Condition = null; + } + } + + tr = info.GetTextEntry(103); // action + if (tr != null && (entry.Action == null || entry.Action.Length < 230)) + { + if (tr.Text != null && tr.Text.Trim().Length > 0) + { + entry.Action = tr.Text; + } + else + { + entry.Action = null; + } + } + + tr = info.GetTextEntry(104); // gump + if (tr != null && (entry.Gump == null || entry.Gump.Length < 230)) + { + if (tr.Text != null && tr.Text.Trim().Length > 0) + { + entry.Gump = tr.Text; + } + else + { + entry.Gump = null; + } + } + + entry.LockConversation = info.IsSwitched(250); // lock conversation + entry.AllowNPCTrigger = info.IsSwitched(251); // allow npc + entry.IgnoreCarried = info.IsSwitched(252); // ignorecarried + } + + + + switch (info.ButtonID) + { + + case 0: // Close + { + + m_Dialog.DeleteTextEntryBook(); + + return; + } + case 100: // toggle running status + { + + m_Dialog.Running = !m_Dialog.Running; + + break; + } + case 155: // add new entry + { + + if (m_SearchList != null) + { + // find the last entry + int lastentry = 0; + foreach(XmlDialog.SpeechEntry e in m_SearchList) + { + if (e.EntryNumber > lastentry) + { + lastentry = e.EntryNumber; + } + } + lastentry += 10; + XmlDialog.SpeechEntry se = new XmlDialog.SpeechEntry(); + se.EntryNumber = lastentry; + se.ID = lastentry; + m_SearchList.Add(se); + Selected = m_SearchList.Count -1; + } + break; + } + + case 156: // Delete selected entries + { + XmlEditDialogGump g = Refresh(state); + int allcount = 0; + if (m_SearchList != null) + { + allcount = m_SearchList.Count; + } + + state.Mobile.SendGump(new XmlConfirmDeleteGump(state.Mobile, g, m_SearchList, m_SelectionList, DisplayFrom, SelectAll, allcount)); + return; + } + + case 159: // save to a .npc file + { + + // Create a new gump + Refresh(state); + // try to save + m_Dialog.DoSaveNPC(state.Mobile, SaveFilename, true); + + return; + } + + case 201: // forward block + { + // clear the selections + if (m_SelectionList != null && !SelectAll) + { + Array.Clear(m_SelectionList,0,m_SelectionList.Length); + } + + if (m_SearchList != null && DisplayFrom + MaxEntries < m_SearchList.Count) + { + DisplayFrom += MaxEntries; + // clear any selection + Selected = -1; + } + break; + } + case 202: // backward block + { + // clear the selections + if (m_SelectionList != null && !SelectAll) + { + Array.Clear(m_SelectionList,0,m_SelectionList.Length); + } + + DisplayFrom -= MaxEntries; + if (DisplayFrom < 0) + { + DisplayFrom = 0; + } + + // clear any selection + Selected = -1; + break; + } + + case 700: // Sort + { + // clear any selection + Selected = -1; + // clear the selections + if (m_SelectionList != null && !SelectAll) + { + Array.Clear(m_SelectionList,0,m_SelectionList.Length); + } + + SortFindList(); + break; + } + + case 9998: // refresh the gump + { + // clear any selection + Selected = -1; + break; + } + default: + { + + if (info.ButtonID >= 1000 && info.ButtonID < 1000+ MaxEntries) + { + // flag the entry selected + Selected = info.ButtonID - 1000; + } + else + if (info.ButtonID == 3998) + { + + SelectAll = !SelectAll; + + // dont allow individual selection with the selectall button selected + if (m_SelectionList != null) + { + for(int i = 0; i < MaxEntries;i++) + { + if (i < m_SelectionList.Length) + { + // only toggle the selection list entries for things that actually have entries + m_SelectionList[i] = SelectAll; + } + else + { + break; + } + } + } + } + else + if (info.ButtonID == 3999) + { + + // dont allow individual selection with the selectall button selected + if (m_SelectionList != null && m_SearchList != null && !SelectAll) + { + for(int i = 0; i < MaxEntries;i++) + { + if (i < m_SelectionList.Length) + { + // only toggle the selection list entries for things that actually have entries + if (m_SearchList.Count - DisplayFrom > i) + { + m_SelectionList[i] = !m_SelectionList[i]; + } + } + else + { + break; + } + } + } + } + else + if (info.ButtonID >= 4000 && info.ButtonID < 4000+ MaxEntries) + { + int i = info.ButtonID - 4000; + // dont allow individual selection with the selectall button selected + if (m_SelectionList != null && i >= 0 && i < m_SelectionList.Length && !SelectAll) + { + // only toggle the selection list entries for things that actually have entries + if (m_SearchList != null && m_SearchList.Count - DisplayFrom > i) + { + m_SelectionList[i] = !m_SelectionList[i]; + } + } + } + else + if (info.ButtonID >= 5000 && info.ButtonID < 5100) + { + + + // text entry book buttons + int textid = info.ButtonID - 5000; + + // entry information + XmlDialog.SpeechEntry entry = null; + + if (m_SearchList != null && Selected >= 0 && Selected + DisplayFrom >= 0 && Selected + DisplayFrom < m_SearchList.Count) + { + entry = (XmlDialog.SpeechEntry)m_SearchList[Selected + DisplayFrom]; + } + + string text = String.Empty; + string title = String.Empty; + switch (textid) + { + case 0: // text + { + if (entry != null) + { + text = entry.Text; + } + + title = "Text"; + break; + } + case 1: // keywords + { + if (entry != null) + { + text = entry.Keywords; + } + + title = "Keywords"; + break; + } + case 2: // condition + { + if (entry != null) + { + text = entry.Condition; + } + + title = "Condition"; + break; + } + case 3: // action + { + if (entry != null) + { + text = entry.Action; + } + + title = "Action"; + break; + } + case 4: // gump + { + if (entry != null) + { + text = entry.Gump; + } + + title = "Gump"; + break; + } + case 5: // trigoncarried + { + text = m_Dialog.TriggerOnCarried; + title = "TrigOnCarried"; + break; + } + case 6: // notrigoncarried + { + text = m_Dialog.NoTriggerOnCarried; + title = "NoTrigOnCarried"; + break; + } + } + + object [] args = new object[6]; + + args[0] = m_Dialog; + args[1] = entry; + args[2] = textid; + args[3] = Selected; + args[4] = DisplayFrom; + args[5] = SaveFilename; + + XmlTextEntryBook book = new XmlTextEntryBook(0, String.Empty, m_Dialog.Name, 20, true); + //XmlTextEntryBook book = new XmlTextEntryBook(0, String.Empty, m_Dialog.Name, 20, true, new XmlTextEntryBookCallback(ProcessXmlEditBookEntry), args); + if (m_Dialog.m_TextEntryBook == null) + { + m_Dialog.m_TextEntryBook = new ArrayList(); + } + m_Dialog.m_TextEntryBook.Add(book); + + book.Title = title; + book.Author = Name; + + // fill the contents of the book with the current text entry data + book.FillTextEntryBook(text); + + // put the book at the location of the player so that it can be opened, but drop it below visible range + book.Visible = false; + book.Movable = false; + book.MoveToWorld(new Point3D(state.Mobile.Location.X,state.Mobile.Location.Y,state.Mobile.Location.Z-100), state.Mobile.Map); + + // Create a new gump + Refresh(state); + + // and open it + book.OnDoubleClick(state.Mobile); + + return; + + } + break; + } + } + // Create a new gump + Refresh(state); + } + + + public class XmlConfirmDeleteGump : Gump + { + private ArrayList SearchList; + private bool [] SelectedList; + private Mobile From; + private int DisplayFrom; + private bool selectAll; + XmlEditDialogGump m_Gump; + + public XmlConfirmDeleteGump(Mobile from, XmlEditDialogGump gump, ArrayList searchlist, bool [] selectedlist, int displayfrom, bool selectall, int allcount) : base (0, 0) + { + SearchList = searchlist; + SelectedList = selectedlist; + DisplayFrom = displayfrom; + selectAll = selectall; + m_Gump = gump; + From = from; + Closable = false; + Draggable = true; + AddPage(0); + AddBackground(10, 200, 200, 130, 5054); + int count = 0; + if (selectall) + { + count = allcount; + } + else + { + for(int i =0;i 0) + { + radiostate = info.Switches[0]; + } + switch (info.ButtonID) + { + + default: + { + if (radiostate == 1 && SearchList != null && SelectedList != null) + { // accept + ArrayList dlist = new ArrayList(); + for(int i = 0;i < SearchList.Count;i++) + { + int index = i-DisplayFrom; + if (index >= 0 && index < SelectedList.Length && SelectedList[index] || selectAll) + { + object o = SearchList[i]; + // delete the entry; + dlist.Add(o); + } + } + + foreach(object o in dlist) + { + SearchList.Remove(o); + } + + // clear the selections + Array.Clear(SelectedList,0,SelectedList.Length); + + if (m_Gump != null) + { + state.Mobile.CloseGump(); + m_Gump.Refresh(state); + } + } + break; + } + } + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlFind.cs b/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlFind.cs new file mode 100644 index 000000000..7ad581eba --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlFind.cs @@ -0,0 +1,2367 @@ +using Server.Accounting; +using Server.Commands; +using Server.Commands.Generic; +using Server.Gumps; +using Server.Items; +using Server.Multis; +using Server.Network; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Server.Engines.Spawners; + +namespace Server.Mobiles; + +public class XmlFindGump : Gump +{ + public class XmlFindThread + { + readonly SearchCriteria m_SearchCriteria; + readonly Mobile m_From; + readonly string m_commandstring; + + public XmlFindThread(Mobile from, SearchCriteria criteria, string commandstring) + { + m_SearchCriteria = criteria; + m_From = from; + m_commandstring = commandstring; + } + + public void XmlFindThreadMain() + { + if (m_From == null) + { + return; + } + + string status_str; + + ArrayList results = Search(m_SearchCriteria, out status_str); + + XmlFindGump gump = new XmlFindGump(m_From, m_From.Location, m_From.Map, true, true, false, + + m_SearchCriteria, + + results, -1, 0, null, m_commandstring, + false, false, false, false, false, false, 0, 0); + + // display the updated gump synched with the main server thread + Core.LoopContext.Post(() => GumpDisplayCallback(m_From, gump, status_str)); + + } + + public void GumpDisplayCallback(Mobile from, XmlFindGump gump, string status_str) + { + if (from != null && !from.Deleted) + { + from.SendGump(gump); + if (status_str != null) + { + from.SendMessage(33, "XmlFind: {0}", status_str); + } + } + } + } + + private const int MaxEntries = 18; + private const int MaxEntriesPerPage = 18; + + public class SearchEntry + { + public bool Selected; + public object Object; + + public SearchEntry(object o) => Object = o; + } + + public class SearchCriteria + { + public bool Dosearchtype; + public bool Dosearchname; + public bool Dosearchrange; + public bool Dosearchregion; + public bool Dosearchspawnentry; + public bool Dosearchspawntype; + public bool Dosearchcondition; + public bool Dosearchfel; + public bool Dosearchtram; + public bool Dosearchmal; + public bool Dosearchilsh; + public bool Dosearchtok; + public bool Dosearchter; + public bool Dosearchint; + public bool Dosearchnull; + public bool Dosearcherr; + public bool Dosearchage; + public bool Dohidevalidint; + public bool Searchagedirection; + public double Searchage; + public int Searchrange; + public string Searchregion; + public string Searchcondition; + public string Searchtype; + public string Searchname; + public string Searchspawnentry; + + public Map Currentmap; + public Point3D Currentloc; + + public SearchCriteria(bool dotype, bool doname, bool dorange, bool doregion, bool doentry, bool doentrytype, bool docondition, bool dofel, bool dotram, + bool domal, bool doilsh, bool dotok, bool doter, bool doint, bool donull, bool doerr, bool doage, bool dohidevalid, + bool agedirection, double age, int range, string region, string condition, string type, string name, string entry + ) + { + Dosearchtype = dotype; + Dosearchname = doname; + Dosearchrange = dorange; + Dosearchregion = doregion; + Dosearchspawnentry = doentry; + Dosearchspawntype = doentrytype; + Dosearchcondition = docondition; + Dosearchfel = dofel; + Dosearchtram = dotram; + Dosearchmal = domal; + Dosearchilsh = doilsh; + Dosearchtok = dotok; + Dosearchter = doter; + Dosearchint = doint; + Dosearchnull = donull; + Dosearcherr = doerr; + Dosearchage = doage; + Dohidevalidint = dohidevalid; + Searchagedirection = agedirection; + Searchage = age; + Searchrange = range; + Searchregion = region; + Searchcondition = condition; + Searchtype = type; + Searchname = name; + Searchspawnentry = entry; + } + + public SearchCriteria() + { + } + } + + private readonly SearchCriteria m_SearchCriteria; + private bool Sorttype; + private bool Sortrange; + private bool Sortname; + private bool Sortmap; + private bool Sortselect; + private readonly Mobile m_From; + private readonly Point3D StartingLoc; + private readonly Map StartingMap; + private bool m_ShowExtension; + private bool Descendingsort; + private int Selected; + private int DisplayFrom; + private string SaveFilename; + private string CommandString; + + private bool SelectAll; + + private ArrayList m_SearchList; + + public static void Initialize() + { + CommandSystem.Register("XmlFind", AccessLevel.GameMaster, XmlFind_OnCommand); + } + + private static bool TestRange(object o, int range, Map currentmap, Point3D currentloc) + { + if (range < 0) + { + return true; + } + + if (o is Item item) + { + if (item.Map != currentmap) + { + return false; + } + + // is the item in a container? + // if so, then check the range of the parent rather than the item + Point3D loc = item.Location; + if (item.Parent != null && item.RootParent != null) + { + if (item.RootParent is Mobile mobile) + { + loc = mobile.Location; + } + else + if (item.RootParent is Container container) + { + loc = container.Location; + } + + } + return Utility.InRange(currentloc, loc, range); + + } + if (o is Mobile mob) + { + if (mob.Map != currentmap) + { + return false; + } + + return Utility.InRange(currentloc, mob.Location, range); + + } + return false; + } + + private static bool TestRegion(object o, string regionname) + { + if (regionname == null) + { + return false; + } + + if (o is Item item) + { + // is the item in a container? + // if so, then check the region of the parent rather than the item + Point3D loc = item.Location; + if (item.Parent != null && item.RootParent != null) + { + if (item.RootParent is Mobile mobile) + { + loc = mobile.Location; + } + else + if (item.RootParent is Container container) + { + loc = container.Location; + } + } + + Region r = Region.Regions.FirstOrDefault(reg => reg.Map == item.Map && !string.IsNullOrEmpty(reg.Name) && string.Equals(reg.Name, regionname, StringComparison.CurrentCultureIgnoreCase)); + + if (r == null) + { + return false; + } + + return r.Contains(loc); + } + + if (o is Mobile mob) + { + Region r = Region.Regions.FirstOrDefault(reg => reg.Map == mob.Map && !string.IsNullOrEmpty(reg.Name) && string.Equals(reg.Name, regionname, StringComparison.CurrentCultureIgnoreCase)); + + if (r == null) + { + return false; + } + + return r.Contains(mob.Location); + + } + + return false; + } + + private static bool TestAge(object o, double age, bool direction) + { + if (age <= 0) + { + return true; + } + + if (o is Mobile mob) + { + if (direction) + { + // true means allow only mobs greater than the age + if (DateTime.UtcNow - mob.Created > TimeSpan.FromHours(age)) + { + return true; + } + } + else + { + // false means allow only mobs less than the age + if (DateTime.UtcNow - mob.Created < TimeSpan.FromHours(age)) + { + return true; + } + } + } + + return false; + } + + private static void IgnoreManagedInternal(object i, ref ArrayList ignoreList) + { + // ignore valid internalized commodity deed items + if (i is CommodityDeed deed && deed.Commodity != null && deed.Commodity.Map == Map.Internal) + { + ignoreList.Add(deed.Commodity); + } + + // ignore valid internalized keyring keys + if (i is KeyRing keyring && keyring.Keys != null) + { + foreach (Key k in keyring.Keys) + { + ignoreList.Add(k); + } + } + + // ignore valid internalized relocatable house items + if (i is BaseHouse house) + { + foreach (RelocatedEntity relEntity in house.RelocatedEntities) + { + if (relEntity.Entity is Item) + { + ignoreList.Add(relEntity.Entity); + } + } + + foreach (VendorInventory inventory in house.VendorInventories) + { + foreach (Item subItem in inventory.Items) + { + ignoreList.Add(subItem); + } + } + } + } + + // test for valid items/mobs on the internal map + private static bool TestValidInternal(object o) + { + if (o is Mobile m) + { + if (m.Map != Map.Internal || m.Account != null || + (m as IMount)?.Rider != null || + m is BaseCreature creature && creature.IsStabled || + m is PlayerVendor && BaseHouse.AllHouses.Any(x => x.InternalizedVendors.Contains(m))) + { + return true; + } + } + else if (o is Item i) + { + // note, in order to test for a vendors display container that contains valid internal map items + if (i.Map != Map.Internal || i.Parent != null || i is Fists or MountItem or EffectItem || i.HeldBy != null || + i is MovingCrate || i.GetType().DeclaringType == typeof(GenericBuyInfo)) + { + return true; + } + + // boat stuffs + if (i is Static && i.Name != null && (i.Name.ToLower() == "weapon pad" || i.Name.ToLower() == "deck")) + { + return true; + } + + // Ship/Vehicle parts + if (i is BaseDockedBoat or BaseBoat or Plank or TillerMan or Hold) + { + return true; + } + + // TODO: Ignores addons, persistence, and other items that are internalized while not in use + } + + return false; + } + + public static ArrayList Search(SearchCriteria criteria, out string status_str) + { + status_str = null; + ArrayList newarray = new ArrayList(); + ArrayList ignoreList = new ArrayList(); + + if (criteria == null) + { + status_str = "Empty search criteria"; + return newarray; + } + + Type targetType = null; + + Map tokunomap = null; + try + { + tokunomap = Map.Parse("Tokuno"); + } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + + // if the type is specified then get the search type + if (criteria.Dosearchtype && criteria.Searchtype != null) + { + targetType = AssemblyHandler.FindTypeByName(criteria.Searchtype); + if (targetType == null) + { + status_str = $"Invalid type: {criteria.Searchtype}"; + return newarray; + } + } + + // do the search through items + + // make a copy so that we dont get enumeration errors if World.Items.Values changes while searching + ArrayList itemarray = null; + + ICollection itemvalues = World.Items.Values; + + lock (itemvalues.SyncRoot) + { + try + { + itemarray = new ArrayList(itemvalues); + } + catch (SystemException e) { status_str = $"Unable to search World.Items: {e.Message}"; } + } + + if (itemarray != null) + { + foreach (Item i in itemarray) + { + bool hastype = false; + bool hasname = false; + bool hasentry = false; + bool hascondition = false; + bool hasrange = false; + bool hasregion = false; + bool hasmap = false; + bool hasspawnerr = false; + bool hasvalidhidden = false; + + if (i == null || i.Deleted) + { + continue; + } + + // this will deal with items that are not on the internal map but hold valid internal items + if (criteria.Dohidevalidint && i.Map != Map.Internal && i.Map != null) + { + IgnoreManagedInternal(i, ref ignoreList); + } + + // check for map + if (i.Map == Map.Felucca && criteria.Dosearchfel || i.Map == Map.Trammel && criteria.Dosearchtram || + i.Map == Map.Malas && criteria.Dosearchmal || i.Map == Map.Ilshenar && criteria.Dosearchilsh || + i.Map == Map.TerMur && criteria.Dosearchter || i.Map == Map.Internal && criteria.Dosearchint || + i.Map == null && criteria.Dosearchnull) + { + hasmap = true; + } + + if (tokunomap != null && i.Map == tokunomap && criteria.Dosearchtok) + { + hasmap = true; + } + + if (!hasmap) + { + continue; + } + + // check for type + if (criteria.Dosearchtype && (i.GetType().IsSubclassOf(targetType) || i.GetType() == targetType)) + { + hastype = true; + } + if (criteria.Dosearchtype && !hastype) + { + continue; + } + + // check for name + if (criteria.Dosearchname && i.Name != null && criteria.Searchname != null && i.Name.ToLower().IndexOf(criteria.Searchname.ToLower()) >= 0) + { + hasname = true; + } + + if (criteria.Dosearchname && !hasname) + { + continue; + } + + // check for valid internal map items + if (criteria.Dohidevalidint && TestValidInternal(i)) + { + hasvalidhidden = true; + + // this will deal with items that are on the internal map and hold valid internal items + IgnoreManagedInternal(i, ref ignoreList); + } + if (criteria.Dohidevalidint && hasvalidhidden) + { + continue; + } + + // check for range + if (criteria.Dosearchrange && TestRange(i, criteria.Searchrange, criteria.Currentmap, criteria.Currentloc)) + { + hasrange = true; + } + if (criteria.Dosearchrange && !hasrange) + { + continue; + } + + // check for region + if (criteria.Dosearchregion && TestRegion(i, criteria.Searchregion)) + { + hasregion = true; + } + if (criteria.Dosearchregion && !hasregion) + { + continue; + } + + // check for condition + if (criteria.Dosearchcondition && criteria.Searchcondition != null) + { + // check the property test + hascondition = BaseXmlSpawner.CheckPropertyString(null, i, criteria.Searchcondition, out status_str); + } + if (criteria.Dosearchcondition && !hascondition) + { + continue; + } + + // check for entry + if (criteria.Dosearchspawnentry) + { + Type targetentrytype = null; + + if (criteria.Dosearchspawntype) + { + targetentrytype = AssemblyHandler.FindTypeByName(criteria.Searchspawnentry.ToLower()); + } + + if (criteria.Searchspawnentry == null || targetentrytype == null && criteria.Dosearchspawntype) + { + hasentry = false; + } + else + { + // see what kind of spawner it is + if (i is XmlSpawner spawner) + { + // search the entries of the spawner + foreach (XmlSpawner.SpawnObject so in spawner.m_SpawnObjects) + { + if (criteria.Dosearchspawntype) + { + // search by entry type + Type type = null; + + if (so.TypeName != null) + { + string[] args = so.TypeName.Split('/'); + string typestr = null; + if (args != null && args.Length > 0) + { + typestr = args[0]; + } + + type = AssemblyHandler.FindTypeByName(typestr); + } + + if (type != null && (type == targetentrytype || type.IsSubclassOf(targetentrytype))) + { + hasentry = true; + break; + } + } + else + { + // search by entry string + if (so.TypeName != null && so.TypeName.ToLower().IndexOf(criteria.Searchspawnentry.ToLower()) >= 0) + { + hasentry = true; + break; + } + } + } + } + else if (i is Spawner spawner1) + { + // search the entries of the spawner + foreach (var entry in spawner1.Entries) + { + string so = entry.SpawnedName; + + if (criteria.Dosearchspawntype) + { + // search by entry type + Type type = null; + + if (so != null) + { + type = AssemblyHandler.FindTypeByName(so); + } + + if (type != null && (type == targetentrytype || type.IsSubclassOf(targetentrytype))) + { + hasentry = true; + break; + } + } + else + { + if (so != null && so.ToLower().IndexOf(criteria.Searchspawnentry.ToLower()) >= 0) + { + hasentry = true; + break; + } + } + } + } + else + { + hasentry = false; + } + } + } + + if (criteria.Dosearchspawnentry && !hasentry) + { + continue; + } + + if (criteria.Dosearcherr && i is XmlSpawner hasSpawn && hasSpawn.status_str != null) + { + hasspawnerr = true; + } + + if (criteria.Dosearcherr && !hasspawnerr) + { + continue; + } + + // satisfied all conditions so add it + newarray.Add(new SearchEntry(i)); + } + } + + // do the search through mobiles + if (!criteria.Dosearcherr) + { + // make a copy so that we dont get enumeration errors if World.Mobiles.Values changes while searching + ArrayList mobilearray = null; + ICollection mobilevalues = World.Mobiles.Values; + lock (mobilevalues.SyncRoot) + { + try + { + mobilearray = new ArrayList(mobilevalues); + } + catch (SystemException e) { status_str = $"Unable to search World.Mobiles: {e.Message}"; } + } + + if (mobilearray != null) + { + foreach (Mobile i in mobilearray) + { + bool hastype = false; + bool hasname = false; + bool hascondition = false; + bool hasrange = false; + bool hasregion = false; + bool hasmap = false; + bool hasage = false; + bool hasvalidhidden = false; + + if (i == null || i.Deleted) + { + continue; + } + + // check for map + if (i.Map == Map.Felucca && criteria.Dosearchfel || i.Map == Map.Trammel && criteria.Dosearchtram || + i.Map == Map.Malas && criteria.Dosearchmal || i.Map == Map.Ilshenar && criteria.Dosearchilsh || + i.Map == Map.TerMur && criteria.Dosearchter || i.Map == Map.Internal && criteria.Dosearchint || + i.Map == null && criteria.Dosearchnull) + { + hasmap = true; + } + + if (tokunomap != null && i.Map == tokunomap && criteria.Dosearchtok) + { + hasmap = true; + } + + if (!hasmap) + { + continue; + } + + // check for range + if (criteria.Dosearchrange && TestRange(i, criteria.Searchrange, criteria.Currentmap, criteria.Currentloc)) + { + hasrange = true; + } + if (criteria.Dosearchrange && !hasrange) + { + continue; + } + + // check for region + if (criteria.Dosearchregion && TestRegion(i, criteria.Searchregion)) + { + hasregion = true; + } + if (criteria.Dosearchregion && !hasregion) + { + continue; + } + + // check for valid internal map mobiles + if (criteria.Dohidevalidint && TestValidInternal(i)) + { + hasvalidhidden = true; + } + if (criteria.Dohidevalidint && hasvalidhidden) + { + continue; + } + + // check for age + if (criteria.Dosearchage && TestAge(i, criteria.Searchage, criteria.Searchagedirection)) + { + hasage = true; + } + if (criteria.Dosearchage && !hasage) + { + continue; + } + + // check for type + if (criteria.Dosearchtype && (i.GetType().IsSubclassOf(targetType) || i.GetType() == targetType)) + { + hastype = true; + } + if (criteria.Dosearchtype && !hastype) + { + continue; + } + + // check for name + if (criteria.Dosearchname && i.Name != null && criteria.Searchname != null && i.Name.ToLower().IndexOf(criteria.Searchname.ToLower()) >= 0) + { + hasname = true; + } + if (criteria.Dosearchname && !hasname) + { + continue; + } + + // check for condition + if (criteria.Dosearchcondition && criteria.Searchcondition != null) + { + // check the property test + hascondition = BaseXmlSpawner.CheckPropertyString(null, i, criteria.Searchcondition, out status_str); + } + if (criteria.Dosearchcondition && !hascondition) + { + continue; + } + + // passed all conditions so add it to the list + + newarray.Add(new SearchEntry(i)); + } + } + } + + ArrayList removelist = new ArrayList(); + for (int i = 0; i < ignoreList.Count; ++i) + { + foreach (SearchEntry se in newarray) + { + if (se.Object == ignoreList[i]) + { + removelist.Add(se); + break; + } + } + } + + foreach (SearchEntry se in removelist) + { + newarray.Remove(se); + } + + return newarray; + } + + [Usage("XmlFind [objecttype] [range]")] + [Description("Finds objects in the world")] + public static void XmlFind_OnCommand(CommandEventArgs e) + { + if (e?.Mobile == null) + { + return; + } + + Account acct = e.Mobile.Account as Account; + int x = 0; + int y = 0; + XmlSpawnerDefaults.DefaultEntry defs = null; + if (acct != null) + { + defs = XmlSpawnerDefaults.GetDefaults(acct.ToString(), e.Mobile.Name); + } + + if (defs != null) + { + x = defs.FindGumpX; + y = defs.FindGumpY; + } + + string typename = "Xmlspawner"; + int range = -1; + bool dorange = false; + + if (e.Arguments.Length > 0) + { + typename = e.Arguments[0]; + } + + if (e.Arguments.Length > 1) + { + dorange = true; + try + { + range = int.Parse(e.Arguments[1]); + } + catch + { + dorange = false; + e.Mobile.SendMessage("Invalid range argument {0}", e.Arguments[1]); + } + } + + e.Mobile.SendGump(new XmlFindGump(e.Mobile, e.Mobile.Location, e.Mobile.Map, typename, range, dorange, x, y)); + } + + public XmlFindGump(Mobile from, Point3D startloc, Map startmap, int x, int y) + : this(from, startloc, startmap, null, x, y) + { + } + + public XmlFindGump(Mobile from, Point3D startloc, Map startmap, string type, int x, int y) + : this(from, startloc, startmap, type, -1, false, x, y) + { + } + + public XmlFindGump(Mobile from, Point3D startloc, Map startmap, string type, int range, bool dorange, int x, int y) + : this(from, startloc, startmap, true, false, false, + + new SearchCriteria( + true, // dotype + false, // doname + dorange, // dorange + false, // doregion + false, // doentry + false, // doentrytype + false, // docondition + true, // dofel + true, // dotram + true, // domal + true, // doilsh + true, // dotok + true, // doter + false, // doint + false, // donull + false, // doerr + false, // doage + false, // dohidevalid + true, // agedirection + 0, // age + range, // range + null, // region + null, // condition + type, // type + null, // name + null // entry + ), + + null, -1, 0, null, null, + false, false, false, false, false, false, x, y) + { + } + + public XmlFindGump( + Mobile from, + Point3D startloc, + Map startmap, + bool firststart, + bool extension, + bool descend, + SearchCriteria criteria, + ArrayList searchlist, + int selected, + int displayfrom, + string savefilename, + string commandstring, + bool sorttype, + bool sortname, + bool sortrange, + bool sortmap, + bool sortselect, + bool selectall, + int X, + int Y + ) + : base(X, Y) + { + + StartingMap = startmap; + StartingLoc = startloc; + if (from != null && !from.Deleted) + { + m_From = from; + if (firststart) + { + StartingMap = from.Map; + StartingLoc = from.Location; + } + } + + SaveFilename = savefilename; + CommandString = commandstring; + SelectAll = selectall; + Sorttype = sorttype; + Sortname = sortname; + Sortrange = sortrange; + Sortmap = sortmap; + Sortselect = sortselect; + DisplayFrom = displayfrom; + Selected = selected; + m_ShowExtension = extension; + Descendingsort = descend; + + m_SearchCriteria = criteria ?? new SearchCriteria(); + + m_SearchList = searchlist; + + // prepare the page + const int height = 500; + + AddPage(0); + if (m_ShowExtension) + { + AddBackground(0, 0, 755, height, 5054); + AddAlphaRegion(0, 0, 755, height); + } + else + { + AddBackground(0, 0, 170, height, 5054); + AddAlphaRegion(0, 0, 170, height); + } + + // ---------------- + // SORT section + // ---------------- + int y = 5; + // add the Sort button + AddButton(5, y, 0xFAB, 0xFAD, 700); + AddLabel(38, y, 0x384, "Sort"); + + // add the sort direction button + if (Descendingsort) + { + AddButton(75, y + 3, 0x15E2, 0x15E6, 701); + AddLabel(95, y, 0x384, "descend"); + } + else + { + AddButton(75, y + 3, 0x15E0, 0x15E4, 701); + AddLabel(95, y, 0x384, "ascend"); + } + y += 22; + // add the Sort on type toggle + AddRadio(5, y, 0xD2, 0xD3, Sorttype, 0); + AddLabel(28, y, 0x384, "type"); + + // add the Sort on name toggle + AddRadio(75, y, 0xD2, 0xD3, Sortname, 1); + AddLabel(98, y, 0x384, "name"); + + y += 20; + // add the Sort on range toggle + AddRadio(5, y, 0xD2, 0xD3, Sortrange, 2); + AddLabel(28, y, 0x384, "range"); + + // add the Sort on map toggle + AddRadio(75, y, 0xD2, 0xD3, Sortmap, 4); + AddLabel(98, y, 0x384, "map"); + + y += 20; + // add the Sort on selected toggle + AddRadio(5, y, 0xD2, 0xD3, Sortselect, 5); + AddLabel(28, y, 0x384, "select"); + + // ---------------- + // SEARCH section + // ---------------- + y = 85; + // add the Search button + AddButton(5, y, 0xFA8, 0xFAA, 3); + AddLabel(38, y, 0x384, "Search"); + + y += 20; + // add the map buttons + AddCheck(5, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchint, 312); + AddLabel(28, y, 0x384, "Int"); + AddCheck(75, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchnull, 314); + AddLabel(98, y, 0x384, "Null"); + + y += 20; + AddCheck(5, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchfel, 308); + AddLabel(28, y, 0x384, "Fel"); + AddCheck(75, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchtram, 309); + AddLabel(98, y, 0x384, "Tram"); + + y += 20; + AddCheck(5, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchmal, 310); + AddLabel(28, y, 0x384, "Mal"); + AddCheck(75, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchilsh, 311); + AddLabel(98, y, 0x384, "Ilsh"); + + y += 20; + AddCheck(5, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchtok, 318); + AddLabel(28, y, 0x384, "Tok"); + AddCheck(75, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchter, 320); + AddLabel(98, y, 0x384, "Ter"); + + y += 20; + // add the hide valid internal map button + AddCheck(5, y, 0xD2, 0xD3, m_SearchCriteria.Dohidevalidint, 316); + AddLabel(28, y, 0x384, "Hide valid internal"); + + // ---------------- + // FILTER section + // ---------------- + y = height - 295; + + // add the search region entry + AddLabel(28, y, 0x384, "region"); + AddImageTiled(70, y, 68, 19, 0xBBC); + AddTextEntry(70, y, 250, 19, 0, 106, m_SearchCriteria.Searchregion); + // add the toggle to enable search region + AddCheck(5, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchregion, 319); + + y += 20; + // add the search age entry + AddLabel(28, y, 0x384, "age"); + //AddImageTiled(80, 220, 50, 23, 0x52); + AddImageTiled(70, y, 45, 19, 0xBBC); + AddTextEntry(70, y, 45, 19, 0, 105, m_SearchCriteria.Searchage.ToString()); + AddLabel(117, y, 0x384, "Hrs"); + // add the toggle to enable search age + AddCheck(5, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchage, 303); + // add the toggle to set the search age test direction + AddCheck(50, y + 2, 0x1467, 0x1468, m_SearchCriteria.Searchagedirection, 302); + + y += 20; + // add the search range entry + AddLabel(28, y, 0x384, "range"); + AddImageTiled(70, y, 45, 19, 0xBBC); + AddTextEntry(70, y, 45, 19, 0, 100, m_SearchCriteria.Searchrange.ToString()); + // add the toggle to enable search range + AddCheck(5, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchrange, 304); + + y += 20; + // add the search type entry + AddLabel(28, y, 0x384, "type"); + // add the toggle to enable search by type + AddCheck(5, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchtype, 305); + //AddImageTiled(5, 285, 135, 23, 0x52); + AddImageTiled(6, y + 20, 132, 19, 0xBBC); + AddTextEntry(6, y + 20, 250, 19, 0, 101, m_SearchCriteria.Searchtype); + + y += 41; + // add the search condition entry + AddLabel(28, y, 0x384, "property test"); + // add the toggle to enable search by condition + AddCheck(5, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchcondition, 315); + //AddImageTiled(5, 285, 135, 23, 0x52); + AddImageTiled(6, y + 20, 132, 19, 0xBBC); + AddTextEntry(6, y + 20, 500, 19, 0, 104, m_SearchCriteria.Searchcondition); + + y += 41; + // add the search name entry + AddLabel(28, y, 0x384, "name"); + // add the toggle to enable search by name + AddCheck(5, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchname, 306); + //AddImageTiled(5, 350, 135, 23, 0x52); + AddImageTiled(6, y + 20, 132, 19, 0xBBC); + AddTextEntry(6, y + 20, 250, 19, 0, 102, m_SearchCriteria.Searchname); + + y += 41; + // add the search spawner entries + AddLabel(28, y, 0x384, "entry"); + // add the toggle to enable search spawner entries + AddCheck(5, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchspawnentry, 307); + + // add the search spawner entries by type + AddLabel(88, y, 0x384, "type"); + // add the toggle to enable search spawner entry types + AddCheck(65, y, 0xD2, 0xD3, m_SearchCriteria.Dosearchspawntype, 326); + + //AddImageTiled(5, 415, 135, 23, 0x52); + AddImageTiled(6, y + 20, 132, 19, 0xBBC); + AddTextEntry(6, y + 20, 250, 19, 0, 103, m_SearchCriteria.Searchspawnentry); + + // add the search spawner errors + AddLabel(140, y, 0x384, "err"); + // add the toggle to enable search spawner entries + AddCheck(117, y, 0xD2, 0xD3, m_SearchCriteria.Dosearcherr, 313); + + // add the Show Map button + //AddButton(5, 450, 0xFAB, 0xFAD, 150, GumpButtonType.Reply, 0); + //AddLabel(38, 450, 0x384, "Map"); + + // ---------------- + // CONTROL section + // ---------------- + + y = height - 25; + // add the Return button + AddButton(72, y, 0xFAE, 0xFAF, 155); + AddLabel(105, y, 0x384, "Return"); + + y = height - 25; + // add the Bring button + AddButton(5, y, 0xFAE, 0xFAF, 154); + AddLabel(38, y, 0x384, "Bring"); + + + // add gump extension button + if (m_ShowExtension) + { + AddButton(720, y + 5, 0x15E3, 0x15E7, 200); + } + else + { + AddButton(150, y + 5, 0x15E1, 0x15E5, 200); + } + + if (m_ShowExtension) + { + AddLabel(143, 5, 0x384, "Gump"); + AddLabel(178, 5, 0x384, "Prop"); + AddLabel(210, 5, 0x384, "Goto"); + AddLabel(250, 5, 0x384, "Name"); + AddLabel(365, 5, 0x384, "Type"); + AddLabel(460, 5, 0x384, "Location"); + AddLabel(578, 5, 0x384, "Map"); + AddLabel(650, 5, 0x384, "Owner"); + + // add the Delete button + AddButton(150, y, 0xFB1, 0xFB3, 156); + AddLabel(183, height - 25, 0x384, "Delete"); + + // add the Reset button + AddButton(230, y, 0xFA2, 0xFA3, 157); + AddLabel(263, y, 0x384, "Reset"); + + // add the Respawn button + AddButton(310, y, 0xFA8, 0xFAA, 158); + AddLabel(343, y, 0x384, "Respawn"); + + // add the xmlsave entry + AddButton(150, y - 25, 0xFA8, 0xFAA, 159); + AddLabel(183, y - 25, 0x384, "Save to file:"); + + AddImageTiled(270, y - 25, 180, 19, 0xBBC); + AddTextEntry(270, y - 25, 180, 19, 0, 300, SaveFilename); + + // add the commandstring entry + AddButton(470, y - 25, 0xFA8, 0xFAA, 160); + AddLabel(503, y - 25, 0x384, "Command:"); + + AddImageTiled(560, y - 25, 180, 19, 0xBBC); + AddTextEntry(560, y - 25, 180, 19, 0, 301, CommandString); + + + // add the page buttons + for (int i = 0; i < MaxEntries / MaxEntriesPerPage; i++) + { + //AddButton(38+i*30, 365, 2206, 2206, 0, GumpButtonType.Page, 1+i); + AddButton(418 + i * 25, height - 25, 0x8B1 + i, 0x8B1 + i, 0, GumpButtonType.Page, 1 + i); + } + + // add the advance pageblock buttons + AddButton(415 + 25 * (MaxEntries / MaxEntriesPerPage), height - 25, 0x15E1, 0x15E5, 201); // block forward + AddButton(395, height - 25, 0x15E3, 0x15E7, 202); // block backward + + // add the displayfrom entry + AddLabel(460, y, 0x384, "Display"); + AddImageTiled(500, y, 60, 21, 0xBBC); + AddTextEntry(501, y, 60, 21, 0, 400, DisplayFrom.ToString()); + AddButton(560, y, 0xFAB, 0xFAD, 9998); + + // display the item list + if (m_SearchList != null) + { + AddLabel(180, y - 50, 68, $"Found {m_SearchList.Count} items/mobiles"); + AddLabel(400, y - 50, 68, + $"Displaying {DisplayFrom}-{(DisplayFrom + MaxEntries < m_SearchList.Count ? DisplayFrom + MaxEntries : m_SearchList.Count)}" + ); + + // count the number of selected objects + int count = 0; + foreach (SearchEntry e in m_SearchList) + { + if (e.Selected) + { + count++; + } + } + AddLabel(600, y - 50, 33, $"Selected {count}"); + } + + // display the select-all-displayed toggle + AddButton(730, 5, 0xD2, 0xD3, 3999); + + AddLabel(610, y, 0x384, "Select All"); + // display the select-all toggle + AddButton(670, y, SelectAll ? 0xD3 : 0xD2, SelectAll ? 0xD2 : 0xD3, 3998); + + for (int i = 0; i < MaxEntries; i++) + { + int index = i + DisplayFrom; + if (m_SearchList == null || index >= m_SearchList.Count) + { + break; + } + + SearchEntry e = (SearchEntry)m_SearchList[index]; + + int page = i / MaxEntriesPerPage; + + if (i % MaxEntriesPerPage == 0) + { + AddPage(page + 1); + // add highlighted page button + //AddImageTiled(235+page*25, 448, 25, 25, 0xBBC); + //AddImage(238+page*25, 450, 0x8B1+page); + } + + // background for search results area + AddImageTiled(235, 22 * (i % MaxEntriesPerPage) + 30, 386, 23, 0x52); + AddImageTiled(236, 22 * (i % MaxEntriesPerPage) + 31, 384, 21, 0xBBC); + + // add the Goto button for each entry + AddButton(205, 22 * (i % MaxEntriesPerPage) + 30, 0xFAE, 0xFAF, 1000 + i); + + object o = e.Object; + + // add the Gump button for spawner entries + if (o is XmlSpawner || o is Spawner) + { + AddButton(145, 22 * (i % MaxEntriesPerPage) + 30, 0xFBD, 0xFBE, 2000 + i); + } + + // add the Props button for each entry + AddButton(175, 22 * (i % MaxEntriesPerPage) + 30, 0xFAB, 0xFAD, 3000 + i); + + string namestr = string.Empty; + string typestr = string.Empty; + string locstr = string.Empty; + string mapstr = string.Empty; + string ownstr = string.Empty; + int texthue = 0; + + if (o is Item) + { + Item item = (Item)e.Object; + // change the color if it is in a container + namestr = item.Name; + string str = item.GetType().ToString(); + if (str != null) + { + string[] arglist = str.Split('.'); + typestr = arglist[arglist.Length - 1]; + } + // check for in container + // if so then display parent loc + // change the color for container held items + if (item.Parent != null) + { + if (item.RootParent is Mobile m) + { + texthue = m.Player ? 44 : 24; + locstr = m.Location.ToString(); + ownstr = m.Name; + } + else if (item.RootParent is Container c) + { + texthue = 5; + locstr = c.Location.ToString(); + ownstr = c.Name ?? c.ItemData.Name; + } + } + else + { + locstr = item.Location.ToString(); + } + + if (item.Deleted) + { + mapstr = "Deleted"; + } + else + if (item.Map != null) + { + mapstr = item.Map.ToString(); + } + } + else if (o is Mobile) + { + Mobile mob = (Mobile)e.Object; + // change the color if it is in a container + namestr = mob.Name; + string str = mob.GetType().ToString(); + if (str != null) + { + string[] arglist = str.Split('.'); + typestr = arglist[arglist.Length - 1]; + } + locstr = mob.Location.ToString(); + if (mob.Deleted) + { + mapstr = "Deleted"; + } + else + if (mob.Map != null) + { + mapstr = mob.Map.ToString(); + } + } + + if (e.Selected) + { + texthue = 33; + } + + if (i == Selected) + { + texthue = 68; + } + + // display the name + AddLabelCropped(248, 22 * (i % MaxEntriesPerPage) + 31, 110, 21, texthue, namestr ?? string.Empty); + + // display the type + AddImageTiled(360, 22 * (i % MaxEntriesPerPage) + 31, 90, 21, 0xBBC); + AddLabelCropped(360, 22 * (i % MaxEntriesPerPage) + 31, 90, 21, texthue, typestr); + // display the loc + AddImageTiled(450, 22 * (i % MaxEntriesPerPage) + 31, 137, 21, 0xBBC); + AddLabel(450, 22 * (i % MaxEntriesPerPage) + 31, texthue, locstr); + // display the map + AddImageTiled(571, 22 * (i % MaxEntriesPerPage) + 31, 70, 21, 0xBBC); + AddLabel(571, 22 * (i % MaxEntriesPerPage) + 31, texthue, mapstr); + // display the owner + AddImageTiled(640, 22 * (i % MaxEntriesPerPage) + 31, 90, 21, 0xBBC); + AddLabelCropped(640, 22 * (i % MaxEntriesPerPage) + 31, 90, 21, texthue, ownstr); + + // display the selection button + + AddButton(730, 22 * (i % MaxEntriesPerPage) + 32, e.Selected ? 0xD3 : 0xD2, e.Selected ? 0xD2 : 0xD3, 4000 + i); + } + } + } + + private void DoGoTo(int index) + { + if (m_From == null || m_From.Deleted) + { + return; + } + + if (m_SearchList != null && index < m_SearchList.Count) + { + object o = ((SearchEntry)m_SearchList[index]).Object; + if (o is Item item) + { + Point3D itemloc; + if (item.Parent != null) + { + if (item.RootParent is Mobile mobile) + { + itemloc = mobile.Location; + } + else if (item.RootParent is Container container) + { + itemloc = container.Location; + } + else + { + return; + } + } + else + { + itemloc = item.Location; + } + if (item.Deleted || item.Map == null || item.Map == Map.Internal) + { + return; + } + + m_From.Location = itemloc; + m_From.Map = item.Map; + } + + else if (o is Mobile mob) + { + if (mob.Deleted || mob.Map == null || mob.Map == Map.Internal) + { + return; + } + + m_From.Location = mob.Location; + m_From.Map = mob.Map; + } + } + } + + private void DoShowGump(int index) + { + if (m_From == null || m_From.Deleted) + { + return; + } + + if (m_SearchList != null && index < m_SearchList.Count) + { + object o = ((SearchEntry)m_SearchList[index]).Object; + if (o is XmlSpawner x1) + { + // dont open anything with a null map null item or deleted + if (x1.Deleted || x1.Map == null || x1.Map == Map.Internal) + { + return; + } + + x1.OnDoubleClick(m_From); + } + else if (o is Spawner x2) + { + if (x2.Deleted || x2.Map == null || x2.Map == Map.Internal) + { + return; + } + + x2.OnDoubleClick(m_From); + } + } + } + + private void DoShowProps(int index) + { + if (m_From == null || m_From.Deleted) + { + return; + } + + if (m_SearchList != null && index < m_SearchList.Count) + { + object o = ((SearchEntry)m_SearchList[index]).Object; + if (o is Item x1) + { + if (x1.Deleted) + { + return; + } + + m_From.SendGump(new PropertiesGump(m_From, x1)); + } + else if (o is Mobile x2) + { + if (x2.Deleted) + { + return; + } + + m_From.SendGump(new PropertiesGump(m_From, x2)); + } + } + } + + private void SortFindList() + { + if (m_SearchList != null && m_SearchList.Count > 0) + { + if (Sorttype) + { + m_SearchList.Sort(new ListTypeSorter(Descendingsort)); + } + else if (Sortname) + { + m_SearchList.Sort(new ListNameSorter(Descendingsort)); + } + else if (Sortmap) + { + m_SearchList.Sort(new ListMapSorter(Descendingsort)); + } + else if (Sortrange) + { + m_SearchList.Sort(new ListRangeSorter(m_From, Descendingsort)); + } + else if (Sortselect) + { + m_SearchList.Sort(new ListSelectSorter(Descendingsort)); + } + } + } + + private class ListTypeSorter : IComparer + { + private readonly bool Dsort; + + public ListTypeSorter(bool descend) => Dsort = descend; + + public int Compare(object e1, object e2) + { + string xstr = (e1 as SearchEntry)?.Object?.GetType().Name; + string ystr = (e2 as SearchEntry)?.Object?.GetType().Name; + + if (Dsort) + { + return string.Compare(ystr, xstr, true); + } + + return string.Compare(xstr, ystr, true); + } + } + + private class ListNameSorter : IComparer + { + private readonly bool Dsort; + + public ListNameSorter(bool descend) => Dsort = descend; + + public int Compare(object e1, object e2) + { + string xstr = (e1 as SearchEntry)?.Object switch + { + Item item => item.Name, + Mobile mobile => mobile.Name, + _ => null + }; + + string ystr = (e2 as SearchEntry)?.Object switch + { + Item item => item.Name, + Mobile mobile => mobile.Name, + _ => null + }; + + if (Dsort) + { + return string.Compare(ystr, xstr, true); + } + + return string.Compare(xstr, ystr, true); + } + } + + private class ListMapSorter : IComparer + { + private readonly bool Dsort; + + public ListMapSorter(bool descend) => Dsort = descend; + + public int Compare(object e1, object e2) + { + string xstr = ((e1 as SearchEntry)?.Object as IEntity)?.Map.Name; + string ystr = ((e2 as SearchEntry)?.Object as IEntity)?.Map.Name; + + if (Dsort) + { + return string.Compare(ystr, xstr, true); + } + + return string.Compare(xstr, ystr, true); + } + } + + private class ListRangeSorter : IComparer + { + private readonly Mobile From; + private readonly bool Dsort; + + public ListRangeSorter(Mobile from, bool descend) + { + From = from; + Dsort = descend; + } + + public int Compare(object e1, object e2) + { + if (From == null || From.Deleted) + { + return 0; + } + + IEntity entity1 = (e1 as SearchEntry)?.Object as IEntity; + IEntity entity2 = (e2 as SearchEntry)?.Object as IEntity; + + if (entity1 == null && entity2 == null) + { + return 0; + } + + if (entity1 == null) + { + return Dsort ? 1 : -1; + } + if (entity2 == null) + { + return Dsort ? -1 : 1; + } + + if (entity1.Map != From.Map && entity2.Map != From.Map) + { + return 0; + } + + if (entity1.Map == From.Map && entity2.Map != From.Map) + { + return Dsort ? 1 : -1; + } + + if (entity1.Map != From.Map && entity2.Map == From.Map) + { + return Dsort ? -1 : 1; + } + + if (Dsort) + { + return From.GetDistanceToSqrt(entity2.Location).CompareTo(From.GetDistanceToSqrt(entity1.Location)); + } + + return From.GetDistanceToSqrt(entity1.Location).CompareTo(From.GetDistanceToSqrt(entity2.Location)); + } + } + + private class ListSelectSorter : IComparer + { + private readonly bool Dsort; + + public ListSelectSorter(bool descend) => Dsort = descend; + + public int Compare(object e1, object e2) + { + int x = 0; + int y = 0; + + if (e1 is SearchEntry entry) + { + x = entry.Selected ? 1 : 0; + } + + if (e2 is SearchEntry searchEntry) + { + y = searchEntry.Selected ? 1 : 0; + } + + if (Dsort) + { + return x - y; + } + + return y - x; + } + } + + private void Refresh(NetState state) + { + state.Mobile.SendGump(new XmlFindGump(m_From, StartingLoc, StartingMap, false, m_ShowExtension, Descendingsort, m_SearchCriteria, m_SearchList, Selected, DisplayFrom, SaveFilename, + CommandString, Sorttype, Sortname, Sortrange, + Sortmap, Sortselect, SelectAll, X, Y)); + } + + private void ResetList() + { + if (m_SearchList == null) + { + return; + } + + for (int i = 0; i < m_SearchList.Count; i++) + { + SearchEntry e = (SearchEntry)m_SearchList[i]; + + if (e.Selected) + { + object o = e.Object; + + if (o is XmlSpawner spawner) + { + spawner.DoReset = true; + } + } + } + } + + private void RespawnList() + { + if (m_SearchList == null) + { + return; + } + + for (int i = 0; i < m_SearchList.Count; i++) + { + SearchEntry e = (SearchEntry)m_SearchList[i]; + + if (e.Selected) + { + object o = e.Object; + + if (o is XmlSpawner spawner) + { + spawner.DoRespawn = true; + } + } + } + } + + private void SaveList(Mobile from, string filename) + { + if (m_SearchList == null) + { + return; + } + + string dirname; + if (System.IO.Directory.Exists(XmlSpawner.XmlSpawnDir) && filename != null && !filename.StartsWith("/") && !filename.StartsWith("\\")) + { + // put it in the defaults directory if it exists + dirname = $"{XmlSpawner.XmlSpawnDir}/{filename}"; + } + else + { + // otherwise just put it in the main installation dir + dirname = filename; + } + + List savelist = new List(); + + for (int i = 0; i < m_SearchList.Count; i++) + { + SearchEntry e = (SearchEntry)m_SearchList[i]; + + if (e.Selected) + { + object o = e.Object; + + if (o is XmlSpawner spawner) + { + // add it to the saves list + savelist.Add(spawner); + } + } + } + + // write out the spawners to a file + XmlSpawner.SaveSpawnList(from, savelist, dirname, false, true); + } + + private void ExecuteCommand(Mobile from, string command) + { + if (m_SearchList == null) + { + return; + } + + var executelist = new List(); + + for (int i = 0; i < m_SearchList.Count; i++) + { + SearchEntry e = (SearchEntry)m_SearchList[i]; + + if (e.Selected) + { + object o = e.Object; + + // add it to the execute list + executelist.Add(o); + + } + } + + // lookup the command + // and execute it + if (!string.IsNullOrEmpty(command)) + { + string[] args = command.Split(' '); + + if (args.Length > 1) + { + string[] cargs = new string[args.Length - 1]; + for (int i = 0; i < args.Length - 1; i++) + { + cargs[i] = args[i + 1]; + } + + CommandEventArgs e = new CommandEventArgs(from, args[0], command, cargs); + + foreach (BaseCommand c in TargetCommands.AllCommands) + { + // find the matching command + if (string.Equals(c.Commands[0], args[0], StringComparison.CurrentCultureIgnoreCase)) + { + bool flushToLog = false; + + // execute the command on the objects in the list + + if (executelist.Count > 20) + { + CommandLogging.Enabled = false; + } + + c.ExecuteList(e, executelist); + + if (executelist.Count > 20) + { + flushToLog = true; + CommandLogging.Enabled = true; + } + + c.Flush(from, flushToLog); + return; + } + } + from.SendMessage("Invalid command: {0}", args[0]); + } + } + } + + public override void OnResponse(NetState state, RelayInfo info) + { + if (info == null || state?.Mobile == null || m_SearchCriteria == null) + { + return; + } + + int radiostate = -1; + if (info.Switches.Length > 0) + { + radiostate = info.Switches[0]; + } + + // read the text entries for the search criteria + TextRelay tr = info.GetTextEntry(105); // range info + m_SearchCriteria.Searchage = 0; + if (tr?.Text != null && tr.Text.Length > 0) + { + try { m_SearchCriteria.Searchage = double.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + // read the text entries for the search criteria + tr = info.GetTextEntry(100); // range info + m_SearchCriteria.Searchrange = -1; + if (tr?.Text != null && tr.Text.Length > 0) + { + try { m_SearchCriteria.Searchrange = int.Parse(tr.Text); } + catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } + } + + tr = info.GetTextEntry(101); // type info + if (tr != null) + { + m_SearchCriteria.Searchtype = tr.Text; + } + + tr = info.GetTextEntry(102); // name info + if (tr != null) + { + m_SearchCriteria.Searchname = tr.Text; + } + + tr = info.GetTextEntry(103); // entry info + if (tr != null) + { + m_SearchCriteria.Searchspawnentry = tr.Text; + } + + tr = info.GetTextEntry(104); // condition info + if (tr != null) + { + m_SearchCriteria.Searchcondition = tr.Text; + } + + tr = info.GetTextEntry(106); // region info + if (tr != null) + { + m_SearchCriteria.Searchregion = tr.Text; + } + + + tr = info.GetTextEntry(400); // displayfrom info + if (tr != null) + { + DisplayFrom = Utility.ToInt32(tr.Text); + } + + tr = info.GetTextEntry(300); // savefilename info + if (tr != null) + { + SaveFilename = tr.Text; + } + + tr = info.GetTextEntry(301); // commandstring info + if (tr != null) + { + CommandString = tr.Text; + } + + + // check all of the check boxes + m_SearchCriteria.Searchagedirection = info.IsSwitched(302); + m_SearchCriteria.Dosearchage = info.IsSwitched(303); + m_SearchCriteria.Dosearchrange = info.IsSwitched(304); + m_SearchCriteria.Dosearchtype = info.IsSwitched(305); + m_SearchCriteria.Dosearchname = info.IsSwitched(306); + m_SearchCriteria.Dosearchspawnentry = info.IsSwitched(307); + m_SearchCriteria.Dosearchspawntype = info.IsSwitched(326); + m_SearchCriteria.Dosearcherr = info.IsSwitched(313); + m_SearchCriteria.Dosearchcondition = info.IsSwitched(315); + + m_SearchCriteria.Dosearchint = info.IsSwitched(312); + m_SearchCriteria.Dosearchfel = info.IsSwitched(308); + m_SearchCriteria.Dosearchtram = info.IsSwitched(309); + m_SearchCriteria.Dosearchmal = info.IsSwitched(310); + m_SearchCriteria.Dosearchilsh = info.IsSwitched(311); + m_SearchCriteria.Dosearchtok = info.IsSwitched(318); + m_SearchCriteria.Dosearchter = info.IsSwitched(320); + m_SearchCriteria.Dosearchnull = info.IsSwitched(314); + + m_SearchCriteria.Dohidevalidint = info.IsSwitched(316); + m_SearchCriteria.Dosearchregion = info.IsSwitched(319); + + switch (info.ButtonID) + { + + case 0: // Close + { + return; + } + case 3: // Search + { + // clear any selection + Selected = -1; + + // reset displayfrom + DisplayFrom = 0; + + // do the search + m_SearchCriteria.Currentloc = state.Mobile.Location; + m_SearchCriteria.Currentmap = state.Mobile.Map; + + //m_SearchList = Search(m_SearchCriteria, out status_str); + XmlFindThread tobj = new XmlFindThread(state.Mobile, m_SearchCriteria, CommandString); + Thread find = new Thread(tobj.XmlFindThreadMain) + { + Name = "XmlFind Thread" + }; + find.Start(); + + // turn on gump extension + m_ShowExtension = true; + return; + } + case 4: // SubSearch + { + // do the search + string status_str; + m_SearchList = Search(m_SearchCriteria, out status_str); + break; + } + case 150: // Open the map gump + { + break; + } + case 154: // Bring all selected objects to the current location + { + Refresh(state); + + state.Mobile.SendGump(new XmlConfirmBringGump(m_SearchList)); + return; + } + case 155: // Return the player to the starting loc + { + m_From.Location = StartingLoc; + m_From.Map = StartingMap; + break; + } + case 156: // Delete selected items + { + Refresh(state); + + state.Mobile.SendGump(new XmlConfirmDeleteGump(m_SearchList)); + return; + } + case 157: // Reset selected items + { + ResetList(); + break; + } + case 158: // Respawn selected items + { + RespawnList(); + break; + } + case 159: // xmlsave selected spawners + { + SaveList(state.Mobile, SaveFilename); + break; + } + case 160: // execute the command on the selected items + { + ExecuteCommand(state.Mobile, CommandString); + break; + } + case 200: // gump extension + { + m_ShowExtension = !m_ShowExtension; + break; + } + case 201: // forward block + { + if (m_SearchList != null && DisplayFrom + MaxEntries < m_SearchList.Count) + { + DisplayFrom += MaxEntries; + // clear any selection + Selected = -1; + } + break; + } + case 202: // backward block + { + + DisplayFrom -= MaxEntries; + if (DisplayFrom < 0) + { + DisplayFrom = 0; + } + + // clear any selection + Selected = -1; + break; + } + + case 700: // Sort + { + // clear any selection + Selected = -1; + + Sorttype = false; + Sortname = false; + Sortrange = false; + Sortmap = false; + Sortselect = false; + // read the toggle switches that determine the sort + if (radiostate == 0) // sort by type + { + Sorttype = true; + } + else + if (radiostate == 1) // sort by name + { + Sortname = true; + } + else + if (radiostate == 2) // sort by range + { + Sortrange = true; + } + else + if (radiostate == 4) // sort by entry + { + Sortmap = true; + } + else + if (radiostate == 5) // sort by selected + { + Sortselect = true; + } + + SortFindList(); + break; + } + case 701: // descending sort + { + Descendingsort = !Descendingsort; + break; + } + case 9998: // refresh the gump + { + // clear any selection + Selected = -1; + break; + } + default: + { + + if (info.ButtonID >= 1000 && info.ButtonID < 1000 + MaxEntries) + { + // flag the entry selected + Selected = info.ButtonID - 1000; + // then go to it + DoGoTo(info.ButtonID - 1000 + DisplayFrom); + } + if (info.ButtonID >= 2000 && info.ButtonID < 2000 + MaxEntries) + { + // flag the entry selected + Selected = info.ButtonID - 2000; + // then open the gump + Refresh(state); + DoShowGump(info.ButtonID - 2000 + DisplayFrom); + return; + } + if (info.ButtonID >= 3000 && info.ButtonID < 3000 + MaxEntries) + { + Selected = info.ButtonID - 3000; + // Show the props window + Refresh(state); + DoShowProps(info.ButtonID - 3000 + DisplayFrom); + return; + } + if (info.ButtonID == 3998) + { + SelectAll = !SelectAll; + + if (m_SearchList != null) + { + foreach (SearchEntry e in m_SearchList) + { + e.Selected = SelectAll; + } + } + } + if (info.ButtonID == 3999) + { + // toggle selection of everything currently displayed + if (m_SearchList != null) + { + for (int i = 0; i < MaxEntries; i++) + { + if (i + DisplayFrom < m_SearchList.Count) + { + SearchEntry e = (SearchEntry)m_SearchList[i + DisplayFrom]; + + e.Selected = !e.Selected; + } + else + { + break; + } + } + } + } + if (info.ButtonID >= 4000 && info.ButtonID < 4000 + MaxEntries) + { + int i = info.ButtonID - 4000; + + if (m_SearchList != null && i >= 0 && m_SearchList.Count > i + DisplayFrom) + { + SearchEntry e = (SearchEntry)m_SearchList[i + DisplayFrom]; + + e.Selected = !e.Selected; + } + } + + break; + } + } + + Refresh(state); + } + + public class XmlConfirmBringGump : Gump + { + private readonly ArrayList SearchList; + + public XmlConfirmBringGump(ArrayList searchlist) + : base(0, 0) + { + SearchList = searchlist; + + Closable = false; + Draggable = true; + AddPage(0); + AddBackground(10, 200, 200, 130, 5054); + int count = 0; + + if (SearchList != null) + { + for (int i = 0; i < SearchList.Count; i++) + { + if (((SearchEntry)SearchList[i]).Selected) + { + count++; + } + } + } + + AddLabel(20, 225, 33, $"Bring {count} objects to you?"); + AddRadio(35, 255, 9721, 9724, false, 1); // accept/yes radio + AddRadio(135, 255, 9721, 9724, true, 2); // decline/no radio + AddHtmlLocalized(72, 255, 200, 30, 1049016, 0x7fff); // Yes + AddHtmlLocalized(172, 255, 200, 30, 1049017, 0x7fff); // No + AddButton(80, 289, 2130, 2129, 3); // Okay button + + } + public override void OnResponse(NetState state, RelayInfo info) + { + if (info == null || state?.Mobile == null) + { + return; + } + + int radiostate = -1; + + Point3D myloc = state.Mobile.Location; + Map mymap = state.Mobile.Map; + + if (info.Switches.Length > 0) + { + radiostate = info.Switches[0]; + } + switch (info.ButtonID) + { + default: + { + if (radiostate == 1 && SearchList != null) + { // accept + for (int i = 0; i < SearchList.Count; i++) + { + SearchEntry e = (SearchEntry)SearchList[i]; + + if (e.Selected) + { + object o = e.Object; + + if (o is Item item) + { + + item.MoveToWorld(myloc, mymap); + + } + else if (o is Mobile mobile) + { + + mobile.MoveToWorld(myloc, mymap); + + } + } + } + } + break; + } + } + } + } + + public class XmlConfirmDeleteGump : Gump + { + private readonly ArrayList SearchList; + + public XmlConfirmDeleteGump(ArrayList searchlist) + : base(0, 0) + { + SearchList = searchlist; + + Closable = false; + Draggable = true; + AddPage(0); + AddBackground(10, 200, 200, 130, 5054); + int count = 0; + + if (SearchList != null) + { + for (int i = 0; i < SearchList.Count; i++) + { + if (((SearchEntry)SearchList[i]).Selected) + { + count++; + } + } + } + + AddLabel(20, 225, 33, $"Delete {count} objects?"); + AddRadio(35, 255, 9721, 9724, false, 1); // accept/yes radio + AddRadio(135, 255, 9721, 9724, true, 2); // decline/no radio + AddHtmlLocalized(72, 255, 200, 30, 1049016, 0x7fff); // Yes + AddHtmlLocalized(172, 255, 200, 30, 1049017, 0x7fff); // No + AddButton(80, 289, 2130, 2129, 3); // Okay button + + } + public override void OnResponse(NetState state, RelayInfo info) + { + if (info == null || state?.Mobile == null) + { + return; + } + + int radiostate = -1; + if (info.Switches.Length > 0) + { + radiostate = info.Switches[0]; + } + switch (info.ButtonID) + { + + default: + { + if (radiostate == 1 && SearchList != null) + { // accept + for (int i = 0; i < SearchList.Count; i++) + { + SearchEntry e = (SearchEntry)SearchList[i]; + + if (e.Selected) + { + object o = e.Object; + + if (o is Item item) + { + // some objects may not delete gracefully (null map items are particularly error prone) so trap them + try + { + item.Delete(); + } + catch (Exception ex) { Diagnostics.ExceptionLogging.LogException(ex); } + } + else if (o is Mobile mobile && !mobile.Player) + { + try + { + mobile.Delete(); + } + catch (Exception ex) { Diagnostics.ExceptionLogging.LogException(ex); } + } + } + } + } + + break; + } + } + } + } +} diff --git a/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlPartialCategorizedAddGump.cs b/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlPartialCategorizedAddGump.cs new file mode 100644 index 000000000..00de759ff --- /dev/null +++ b/Projects/UOContent/Engines/XMLSpawner/XmlUtils/XmlPartialCategorizedAddGump.cs @@ -0,0 +1,266 @@ +using Server.Mobiles; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Server.Gumps; + +public class XmlPartialCategorizedAddGump : Gump +{ + private readonly string m_SearchString; + private readonly ArrayList m_SearchResults; + private readonly int m_Page; + private readonly Gump m_Gump; + private readonly int m_EntryIndex; + private readonly XmlSpawner m_Spawner; + + public XmlPartialCategorizedAddGump(Mobile from, string searchString, int page, ArrayList searchResults, bool explicitSearch, int entryindex, Gump gump) : base(50, 50) + { + if (gump is XmlSpawnerGump spawnerGump) + { + // keep track of the spawner for xmlspawnergumps + m_Spawner = spawnerGump.m_Spawner; + } + + // keep track of the gump + m_Gump = gump; + + + m_SearchString = searchString; + m_SearchResults = searchResults; + m_Page = page; + + m_EntryIndex = entryindex; + + from.CloseGump(); + + AddPage(0); + + AddBackground(0, 0, 420, 280, 5054); + + AddImageTiled(10, 10, 400, 20, 2624); + AddAlphaRegion(10, 10, 400, 20); + AddImageTiled(41, 11, 184, 18, 0xBBC); + AddImageTiled(42, 12, 182, 16, 2624); + AddAlphaRegion(42, 12, 182, 16); + + AddButton(10, 9, 4011, 4013, 1); + AddTextEntry(44, 10, 180, 20, 0x480, 0, searchString); + + AddHtmlLocalized(230, 10, 100, 20, 3010005, 0x7FFF); + + AddImageTiled(10, 40, 400, 200, 2624); + AddAlphaRegion(10, 40, 400, 200); + + if (searchResults.Count > 0) + { + for (int i = page * 10; i < (page + 1) * 10 && i < searchResults.Count; ++i) + { + int index = i % 10; + + SearchEntry se = (SearchEntry)searchResults[i]; + + string labelstr = se.EntryType.Name; + + if (se.Parameters.Length > 0) + { + for (int j = 0; j < se.Parameters.Length; j++) + { + labelstr += $", {se.Parameters[j].Name}"; + } + } + + AddLabel(44, 39 + index * 20, 0x480, labelstr); + AddButton(10, 39 + index * 20, 4023, 4025, 4 + i); + } + } + else + { + AddLabel(15, 44, 0x480, explicitSearch ? "Nothing matched your search terms." : "No results to display."); + } + + AddImageTiled(10, 250, 400, 20, 2624); + AddAlphaRegion(10, 250, 400, 20); + + if (m_Page > 0) + { + AddButton(10, 249, 4014, 4016, 2); + } + else + { + AddImage(10, 249, 4014); + } + + AddHtmlLocalized(44, 250, 170, 20, 1061028, m_Page > 0 ? 0x7FFF : 0x5EF7); // Previous page + + if ((m_Page + 1) * 10 < searchResults.Count) + { + AddButton(210, 249, 4005, 4007, 3); + } + else + { + AddImage(210, 249, 4005); + } + + AddHtmlLocalized(244, 250, 170, 20, 1061027, (m_Page + 1) * 10 < searchResults.Count ? 0x7FFF : 0x5EF7); // Next page + } + + private static readonly Type typeofItem = typeof(Item), typeofMobile = typeof(Mobile); + + private class SearchEntry + { + public Type EntryType; + public ParameterInfo[] Parameters; + } + private static void Match(string match, IReadOnlyList types, IList results) + { + if (match.Length == 0) + { + return; + } + + match = match.ToLower(); + + for (int i = 0; i < types.Count; ++i) + { + Type t = types[i]; + + if ((typeofMobile.IsAssignableFrom(t) || typeofItem.IsAssignableFrom(t)) && t.Name.ToLower().IndexOf(match) >= 0 && !results.Contains(t)) + { + ConstructorInfo[] ctors = t.GetConstructors(); + + for (int j = 0; j < ctors.Length; ++j) + { + if (/*ctors[j].GetParameters().Length == 0 && */ ctors[j].IsDefined(typeof(ConstructibleAttribute), false)) + { + SearchEntry s = new SearchEntry + { + EntryType = t, + Parameters = ctors[j].GetParameters() + }; + //results.Add(t); + results.Add(s); + //break; + } + } + } + } + } + + public static ArrayList Match(string match) + { + ArrayList results = new ArrayList(); + Type[] types; + + Assembly[] asms = AssemblyHandler.Assemblies; + + for (int i = 0; i < asms.Length; ++i) + { + types = AssemblyHandler.GetTypeCache(asms[i]).Types; + Match(match, types, results); + } + + types = AssemblyHandler.GetTypeCache(Core.Assembly).Types; + Match(match, types, results); + + results.Sort(new TypeNameComparer()); + + return results; + } + + private class TypeNameComparer : IComparer + { + public int Compare(object x, object y) + { + SearchEntry a = x as SearchEntry; + SearchEntry b = y as SearchEntry; + + return a.EntryType.Name.CompareTo(b.EntryType.Name); + } + } + + + public override void OnResponse(Network.NetState sender, RelayInfo info) + { + Mobile from = sender.Mobile; + + switch (info.ButtonID) + { + case 1: // Search + { + TextRelay te = info.GetTextEntry(0); + string match = te == null ? "" : te.Text.Trim(); + + if (match.Length < 3) + { + from.SendMessage("Invalid search string."); + from.SendGump(new XmlPartialCategorizedAddGump(from, match, m_Page, m_SearchResults, false, m_EntryIndex, m_Gump)); + } + else + { + from.SendGump(new XmlPartialCategorizedAddGump(from, match, 0, Match(match), true, m_EntryIndex, m_Gump)); + } + + break; + } + case 2: // Previous page + { + if (m_Page > 0) + { + from.SendGump(new XmlPartialCategorizedAddGump(from, m_SearchString, m_Page - 1, m_SearchResults, true, m_EntryIndex, m_Gump)); + } + + break; + } + case 3: // Next page + { + if ((m_Page + 1) * 10 < m_SearchResults.Count) + { + from.SendGump(new XmlPartialCategorizedAddGump(from, m_SearchString, m_Page + 1, m_SearchResults, true, m_EntryIndex, m_Gump)); + } + + break; + } + default: + { + int index = info.ButtonID - 4; + + if (index >= 0 && index < m_SearchResults.Count) + { + Type type = ((SearchEntry)m_SearchResults[index]).EntryType; + + if (m_Gump is XmlAddGump mXmlAddGump && type != null) + { + if (mXmlAddGump.defs?.NameList != null && m_EntryIndex >= 0 && m_EntryIndex < mXmlAddGump.defs.NameList.Length) + { + mXmlAddGump.defs.NameList[m_EntryIndex] = type.Name; + XmlAddGump.Refresh(from, true); + } + } + else if (m_Spawner != null && type != null) + { + XmlSpawnerGump xg = m_Spawner.SpawnerGump; + + if (xg != null) + { + + xg.Rentry = new XmlSpawnerGump.ReplacementEntry + { + Typename = type.Name, + Index = m_EntryIndex, + Color = 0x1436 + }; + + Timer.DelayCall(TimeSpan.Zero, XmlSpawnerGump.RefreshSpawnerGumps, from); + //from.CloseGump(); + //from.SendGump(new XmlSpawnerGump(xg.m_Spawner, xg.X, xg.Y, xg.m_ShowGump, xg.xoffset, xg.page, xg.Rentry)); + } + } + } + + break; + } + } + } +}