//#define TRACE
//#define RUNUO2RC1
//#define RESTRICTCONSTRUCTABLE

using System;
using System.Data;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using Server;
using Server.Items;
using Server.Network;
using Server.Gumps;
using Server.Targeting;
using System.Reflection;
using Server.Commands;
using Server.Commands.Generic;
using CPA = Server.CommandPropertyAttribute;
using System.Xml;
using Server.Spells;
using System.Text;
using Server.Accounting;
using System.Diagnostics;
using Server.Misc;
using Server.Engines.XmlSpawner2;

/*
** XmlSpawner2
** version 3.24
** updated 2/11/08
** ArteGordon
** Modification of the original XmlSpawner written by bobsmart
*/

namespace Server.Mobiles
{

    public class XmlSpawner : Item, ISpawner
    {

        #region Type declarations

        public enum TODModeType { Realtime, Gametime }

        public enum SpawnPositionType { Random, RowFill, ColFill, Perimeter, Player, Waypoint, RelXY, DeltaLocation, Location, Wet, Tiles, NoTiles }

        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;
            }
        }

        #endregion

        #region Constant declarations

        public const string Version = "3.24";
        //private const double SpawnIdleTime = 72.0;              // time in hours after which idle spawns will be relocated. A value < 0 disables this feature. This does not work properly under RunUO 2.0
        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 = "Spawns";            // default directory for saving/loading .xml files with [xmlload [xmlsave
        private static string XmlConfigsDir = "SpawnerConfigs";  // default directory for loading .xml config files with LoadConfig
        private const int MaxSmartSectorListSize = 1024;		// maximum sector list size for use in smart spawning. This gives a 512x512 tile range.

        private static string defwaypointname = null;            // 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(RESTRICTCONSTRUCTABLE)
        public static AccessLevel ConstructableAccessLevel = AccessLevel.GameMaster; // only allow spawning of objects that have Constructable access restrictions at this level or lower. Must define RESTRICTCONSTRUCTABLE to enable this.
#endif
        private static int MaxMoveCheck = 10; // limit number of players that can be checked for triggering in a single OnMovement tick

        #endregion

        #region Static variable declarations

        // 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.Owner;

        // 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 = false;
        private static int defTeam = 0;
        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 int defKillReset = 1;
        private static TODModeType defTODMode = TODModeType.Realtime;

        private static Timer m_GlobalSectorTimer;
        private static bool SmartSpawningSystemEnabled = false;

        private static WarnTimer2 m_WarnTimer;

        // hash table for optimizing HoldSmartSpawning method invocation
        private static Hashtable holdSmartSpawningHash;

        public static int seccount;

        // sector hashtable for each map
        private static Hashtable[] GlobalSectorTable = new Hashtable[6];

        #endregion

        #region Variable declarations

        private string m_Name = string.Empty;
        private string m_UniqueId = string.Empty;
        private bool m_PlayerCreated = false;
        private bool m_HomeRangeIsRelative = false;
        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 ArrayList m_SpawnObjects = new ArrayList(); // 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 = false;
        private int m_KillReset = defKillReset;      // number of spawn ticks that pass without kills before killcount gets reset to zero
        private int m_spawncheck = 0;
        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 ArrayList m_TextEntryBook;
        private XmlSpawnerGump m_SpawnerGump;

        private bool m_AllowGhostTriggering = false;
        private bool m_AllowNPCTriggering = false;
        private string m_ConfigFile;
        private bool m_OnHold = false;
        private bool m_HoldSequence = false;
        private bool m_SpawnOnTrigger = false;

        private DateTime m_FirstModified;
        private DateTime m_LastModified;

        private ArrayList m_MovementList;
        private MovementTimer m_MovementTimer;
        internal ArrayList m_KeywordTagList = new ArrayList();
        private string m_FirstModifiedBy = null;
        private string m_LastModifiedBy = null;

        public ArrayList RecentSpawnerSearchList = null;
        public ArrayList RecentItemSearchList = null;
        public ArrayList RecentMobileSearchList = null;
        private TimeSpan m_DespawnTime;

        private string m_SkillTrigger;
        private bool m_skillTriggerActivated;
        private SkillName m_skill_that_triggered;
        private bool m_FreeRun = false;     // override for all other triggering modes
        private SkillName m_SkillTriggerName;
        private double m_SkillTriggerMin;
        private double m_SkillTriggerMax;
        private int m_SkillTriggerSuccess;
        private Map currentmap;

        public bool m_IsInactivated = false;
        private bool m_SmartSpawning = false;
        private SectorTimer m_SectorTimer;

        private ArrayList m_ShowBoundsItems = new ArrayList();

        public ArrayList PropertyInfoList = null;   // used to optimize property info lookup used by set and get property methods.

        private Hashtable spawnPositionWayTable = null;  // used to optimize #waypoint lookup

        private bool inrespawn = false;

        private ArrayList sectorList = null;

        private bool m_Debug;

        private Point3D mostRecentSpawnPosition = Point3D.Zero;

        private int m_MovingPlayerCount = 0;
        private int m_FastestPlayerSpeed = 0;

        private bool m_DebugThis = false;

        private TimerPriority m_BasePriority = TimerPriority.OneSecond;


        #endregion

        #region Property Overrides

        // does not decay
        public override bool Decays { get { return false; } }
        // is not counted in the normal item count
        public override bool IsVirtualItem { get { return true; } }

        #endregion

        #region Properties

        public TimerPriority BasePriority { get { return m_BasePriority; } set { m_BasePriority = value; } }

        public bool DebugThis { get { return m_DebugThis; } set { m_DebugThis = value; } }

        public int MovingPlayerCount { get { return m_MovingPlayerCount; } set { m_MovingPlayerCount = value; } }

        public int FastestPlayerSpeed { get { return m_FastestPlayerSpeed; } set { m_FastestPlayerSpeed = value; } }

        public int NearbyPlayerCount
        {
            get
            {
                int count = 0;
                if (ProximityRange >= 0)
                {
                    foreach (Mobile m in GetMobilesInRange(ProximityRange))
                    {
                        if (m != null && m.Player) count++;
                    }
                }
                return count;
            }
        }

        public Point3D MostRecentSpawnPosition
        {
            get { return mostRecentSpawnPosition; }
            set { mostRecentSpawnPosition = value; }
        }

        public TimeSpan GameTOD
        {
            get
            {
                int hours;
                int minutes;

                Server.Items.Clock.GetTime(this.Map, this.Location.X, this.Location.Y, out  hours, out  minutes);
                return (new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, hours, minutes, 0).TimeOfDay);
            }
        }

        public TimeSpan RealTOD { get { return DateTime.Now.TimeOfDay; } }

        public int RealDay { get { return DateTime.Now.Day; } }

        public int RealMonth { get { return DateTime.Now.Month; } }

        public DayOfWeek RealDayOfWeek { get { return DateTime.Now.DayOfWeek; } }

        public MoonPhase MoonPhase
        {
            get
            {
                return Clock.GetMoonPhase(this.Map, this.Location.X, this.Location.Y);
            }

        }

        public XmlSpawnerGump SpawnerGump
        {
            get { return m_SpawnerGump; }
            set
            {
                m_SpawnerGump = value;
            }
        }

        public bool Debug { get { return m_Debug; } set { m_Debug = value; } }

        public bool DoDefrag
        {
            get { return false; }
            set
            {
                if (value == true)
                {
                    Defrag(true);
                }
            }
        }

        private bool sectorIsActive = false;
        private bool UseSectorActivate = false;

        public bool SingleSector { get { return UseSectorActivate; } }

        /*
        public override void OnSectorDeactivate()
        {
            sectorIsActive = false;
            base.OnSectorDeactivate();
        }

        public override void OnSectorActivate()
        {
            sectorIsActive = true;

            base.OnSectorActivate();

            // perform the smart respawning
            if(SmartSpawning && IsInactivated && UseSectorActivate)
            {
                SmartRespawn();
            }

        }
        */

        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)
                        {
                            BaseCreature b = (BaseCreature)o;

                            // if the mob is damaged or outside of smartspawning detection range then return true
                            if ((b.Hits < b.HitsMax) || (b.Mana < b.ManaMax) || (b.Stam < b.StamMax) || (b.Map != this.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 (b.Map != null && b.Map != Map.Internal)
                            {
                                Sector bsec = b.Map.GetSector(b.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 = 0;


        public bool HasActiveSectors
        {
            get
            {
                if (!SmartSpawning || Map == null || Map == Map.Internal) return false;

                // is this a region spawner?
                if (m_Region != null)
                {
                    List<Mobile> 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 = this.Location;
                    sectorList = new ArrayList();

                    // is this container held?
                    if (Parent != null)
                    {
                        if (RootParent is Mobile)
                            loc = ((Mobile)(RootParent)).Location;
                        else
                            if (RootParent is 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 * Server.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 += Server.Map.SectorSize)
                    {
                        for (int y = y1; y <= y1 + height; y += Server.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 Hashtable();
                                }

                                // add this sector and the spawner associated with it to the global sector table
                                if (GlobalSectorTable[Map.MapID].Contains(s))
                                {
                                    ArrayList spawnerlist = (ArrayList)GlobalSectorTable[Map.MapID][s];
                                    if (spawnerlist == null)
                                    {
                                        //GlobalSectorTable[Map.MapID].Remove(s);
                                        spawnerlist = new ArrayList();
                                        //GlobalSectorTable[Map.MapID].Add(s, spawnerlist);
                                        GlobalSectorTable[Map.MapID][s] = spawnerlist;
                                    }

                                    if (!spawnerlist.Contains(this))
                                    {
                                        spawnerlist.Add(this);

                                    }
                                }
                                else
                                {
                                    ArrayList spawnerlist = new ArrayList();
                                    spawnerlist.Add(this);
                                    // add a new entry to the table
                                    GlobalSectorTable[Map.MapID].Add(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.Now, loc, Map);
                                            op.WriteLine();
                                        }
                                    }
                                    catch
                                    { }

                                    return true;
                                }
                            }

                        }
                    }

                    // if the spawner is in a single sector, then we can use the OnSectorActivation method to test for activity
                    // note, sectors will activate when within +-2 sectors
                    /*
                                        if((sectorList.Count == 1) && (Location.X >= m_X) && (Location.X <= m_X + m_Width) &&
                                            (Location.Y >= m_Y) && (Location.Y <= m_Y + m_Height))
                                        {

                                            UseSectorActivate = true;
                                        }
                                        else
                                        {
                                            UseSectorActivate = false;
                                        }
                                        */
                    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.Players != null && s.Players.Count > 0)
                    {

                        // confirm that players with the proper access level are present
                        foreach (Mobile m in s.Players)
                        {
                            if (m != null && m.AccessLevel <= SmartSpawnAccessLevel || !m.Hidden)
                            {
                                return true;
                            }
                        }
                        _TraceEnd(2);
                    }
                    seccount++;
                }
                _TraceEnd(2);
                return false;
            }
        }

        public int SecCount { get { return seccount; } }


        public bool IsInactivated
        {
            get { return m_IsInactivated; }
            set
            {
                m_IsInactivated = value;
            }
        }


        public int ActiveSectorCount
        {
            get
            {
                if (sectorList != null) return sectorList.Count;
                return 0;
            }
        }

        public DateTime FirstModified
        {
            get { return m_FirstModified; }
        }

        public DateTime LastModified
        {
            get { return m_LastModified; }
        }

        public bool PlayerCreated
        {
            get { return 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 { return null; }
            set
            {
                if (value.Length > 0)
                {
                    string str = value.Trim();
                    string typestr = BaseXmlSpawner.ParseObjectType(str);

                    Type type = null;

                    if (typestr != null)
                        type = SpawnerType.GetType(typestr);

                    if (type != null)
                        m_SpawnObjects.Add(new XmlSpawner.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 XmlSpawner.SpawnObject(str, 1));
                        }
                        else
                            this.status_str = String.Format("{0} is not a valid type name.", str);
                    }
                    InvalidateProperties();
                }
            }
        }


        public Skill TriggerSkill
        {
            get
            {
                if (TriggerMob != null && (int)m_skill_that_triggered >= 0)
                {
                    return TriggerMob.Skills[m_skill_that_triggered];
                }
                else
                {
                    return null;
                }
            }
            set
            {
                if (value != null)
                {
                    m_skill_that_triggered = value.SkillName;
                }
                else
                {
                    m_skill_that_triggered = XmlSpawnerSkillCheck.RegisteredSkill.Invalid;
                }
            }
        }

        public string UniqueId
        {
            get { return m_UniqueId; }
        }

        // does not perform a defrag, so less accurate but can be used while looping through world object enums
        public int SafeCurrentCount
        {
            get { return SafeTotalSpawnedObjects; }
        }


        public bool FreeRun
        {
            get { return 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 &&
                    (m_ObjectPropertyName == null || m_ObjectPropertyName.Length == 0) &&
                    (m_MobPropertyName == null || m_MobPropertyName.Length == 0 ||
                    m_MobTriggerName == null || m_MobTriggerName.Length == 0) &&
                    !m_ExternalTriggering)
                    return true;
                else
                    return false;
            }
        }


        public SpawnObject[] SpawnObjects
        {
            get { return (SpawnObject[])m_SpawnObjects.ToArray(typeof(SpawnObject)); }
            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 == false)
                        {
                            // 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 == true)
                {
                    if (TotalSpawnedObjects <= 0)
                        return true;
                    else
                        return false;
                }
                else
                {
                    if (IsFull)
                        return false;
                    else
                        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 int TotalSpawnObjectCount
        {
            get
            {
                int count = 0;

                foreach (SpawnObject so in m_SpawnObjects)
                    count += so.MaxCount;

                return count;
            }
        }

        #endregion

        #region Command Properties

        [CommandProperty(AccessLevel.GameMaster)]
        public bool GumpReset
        {

            set
            {
                if (value == true)
                {
                    m_SpawnerGump = null;
                }
            }
        }

        public Region SpawnRegion
        {
            get { return m_Region; }
            set
            {
                // force a re-update of the smart spawning sector list the next time it is accessed
                ResetSectorList();

                m_Region = value;

                if (m_Region != null)
                {
                    m_RegionName = m_Region.Name;
                }
                else
                {
                    m_RegionName = null;
                }
            }
        }

        // 2004.02.08 :: Omega Red
        [CommandProperty(AccessLevel.GameMaster)]
        public string RegionName
        {
            get
            {
                return m_RegionName;
            }
            set
            {
                // force a re-update of the smart spawning sector list the next time it is accessed
                ResetSectorList();

                m_RegionName = value;

                if (value == null || value.Length <= 0)
                {
                    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 { return new Point3D(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;

                // Check if the spawner is showing its bounds
                if (ShowBounds == true)
                {
                    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 { return (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;

                //InvalidateProperties();

                // Check if the spawner is showing its bounds
                if (ShowBounds == true)
                {
                    ShowBounds = false;
                    ShowBounds = true;
                }
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool ShowBounds
        {
            get { return (m_ShowBoundsItems != null && m_ShowBoundsItems.Count > 0); }
            set
            {
                if ((value == true) && (ShowBounds == false))
                {
                    if (m_ShowBoundsItems == null) m_ShowBoundsItems = new ArrayList();

                    // 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);
                                s.Visible = false;
                                s.MoveToWorld(new Point3D(NewX, NewY, Z), this.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 Point3D X2_Y2
        {
            get { return new Point3D((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();

                // Check if the spawner is showing its bounds
                if (ShowBounds == true)
                {
                    ShowBounds = false;
                    ShowBounds = true;
                }
            }
        }
        /*
        [CommandProperty(AccessLevel.GameMaster)]
        public override string Name
        {
            get { return m_Name; }
            set
            {
                m_Name = value;
                InvalidateProperties();
            }
        }
        */

        [CommandProperty(AccessLevel.GameMaster)]
        public int MaxCount
        {
            get { return m_Count; }
            set
            {
                m_Count = value;
                InvalidateProperties();
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public int CurrentCount
        {
            get { return TotalSpawnedObjects; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public WayPoint WayPoint
        {
            get { return m_WayPoint; }
            set { m_WayPoint = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool ExternalTriggering
        {
            get { return m_ExternalTriggering; }
            set { m_ExternalTriggering = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool ExtTrigState
        {
            get { return m_ExternalTrigger; }
            set { m_ExternalTrigger = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool Running
        {
            get { return m_Running; }
            set
            {
                // Don't start the spawner unless the height and width are valid
                if ((value == true) && (m_Width >= 0) && (m_Height >= 0))
                {
                    Start();
                }
                else
                    Stop();

                InvalidateProperties();
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public int HomeRange
        {
            get { return m_HomeRange; }
            set { m_HomeRange = value; InvalidateProperties(); }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool HomeRangeIsRelative
        {
            get { return m_HomeRangeIsRelative; }
            set { m_HomeRangeIsRelative = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public int Team
        {
            get { return m_Team; }
            set { m_Team = value; InvalidateProperties(); }
        }
        [CommandProperty(AccessLevel.GameMaster)]
        public int StackAmount
        {
            get { return m_StackAmount; }
            set { m_StackAmount = value; }
        }
        [CommandProperty(AccessLevel.GameMaster)]
        public TimeSpan MinDelay
        {
            get { return m_MinDelay; }
            set
            {
                m_MinDelay = value;
                // reset the spawn timer
                DoTimer();
                InvalidateProperties();
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public TimeSpan MaxDelay
        {
            get { return m_MaxDelay; }
            set
            {
                m_MaxDelay = value;
                // reset the spawn timer
                DoTimer();
                InvalidateProperties();
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public int KillCount
        {
            get { return m_killcount; }
            set { m_killcount = value; }
        }
        [CommandProperty(AccessLevel.GameMaster)]
        public int KillReset
        {
            get { return m_KillReset; }
            set { m_KillReset = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public double TriggerProbability
        {
            get { return m_TriggerProbability; }
            set { m_TriggerProbability = value; }
        }

        //added refractory period support
        [CommandProperty(AccessLevel.GameMaster)]
        public TimeSpan RefractMin
        {
            get { return m_MinRefractory; }
            set { m_MinRefractory = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public TimeSpan RefractMax
        {
            get { return m_MaxRefractory; }
            set { m_MaxRefractory = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public TimeSpan RefractoryOver
        {
            get
            {
                if (m_refractActivated)
                    return m_RefractEnd - DateTime.Now;
                else
                    return TimeSpan.FromSeconds(0);
            }
            set
            {
                DoTimer3(value);
            }
        }
        [CommandProperty(AccessLevel.GameMaster)]
        public string TriggerOnCarried
        {
            get { return m_ItemTriggerName; }
            set { m_ItemTriggerName = value; }

        }
        [CommandProperty(AccessLevel.GameMaster)]
        public string NoTriggerOnCarried
        {
            get { return m_NoItemTriggerName; }
            set { m_NoItemTriggerName = value; }

        }


        [CommandProperty(AccessLevel.GameMaster)]
        public string TriggerObjectProp
        {
            get { return m_ObjectPropertyName; }
            set { m_ObjectPropertyName = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public string TriggerObjectName
        {
            get
            {
                if (m_ObjectPropertyItem == null || m_ObjectPropertyItem.Deleted) return null;
                return m_ObjectPropertyItem.Name;
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public Item TriggerObject
        {
            get { return m_ObjectPropertyItem; }
            set { m_ObjectPropertyItem = 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
            {
                return m_SetPropertyItem;
            }
            set { m_SetPropertyItem = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public string MobTriggerProp
        {
            get { return m_MobPropertyName; }
            set { m_MobPropertyName = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public string MobTriggerName
        {
            get { return 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 { return m_PlayerPropertyName; }
            set { m_PlayerPropertyName = value; }
        }

        // time of day activation
        [CommandProperty(AccessLevel.GameMaster)]
        public TimeSpan TODStart
        {
            get { return m_TODStart; }
            set { m_TODStart = value; }

        }

        [CommandProperty(AccessLevel.GameMaster)]
        public TimeSpan TODEnd
        {
            get { return m_TODEnd; }
            set { m_TODEnd = value; }

        }
        [CommandProperty(AccessLevel.GameMaster)]
        public TimeSpan TOD
        {
            get
            {
                if (m_TODMode == TODModeType.Gametime)
                {
                    int hours;
                    int minutes;
                    Server.Items.Clock.GetTime(this.Map, Location.X, this.Location.Y, out  hours, out  minutes);
                    return (new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, hours, minutes, 0).TimeOfDay);

                }
                else
                    return DateTime.Now.TimeOfDay;

            }

        }

        [CommandProperty(AccessLevel.GameMaster)]
        public TODModeType TODMode
        {
            get { return m_TODMode; }
            set { m_TODMode = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool TODInRange
        {
            get
            {
                if (m_TODStart == m_TODEnd) return true;
                DateTime now;
                DateTime TOD_start;
                DateTime TOD_end;
                DateTime day_start;

                if (m_TODMode == TODModeType.Gametime)
                {
                    int hours;
                    int minutes;
                    Server.Items.Clock.GetTime(this.Map, Location.X, this.Location.Y, out  hours, out  minutes);
                    now = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, hours, minutes, 0);
                }
                else
                {
                    // calculate the time window
                    now = DateTime.Now;
                }
                day_start = new DateTime(now.Year, now.Month, now.Day);
                // calculate the starting TOD window by adding the TODStart to day_start
                TOD_start = day_start + m_TODStart;
                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;
                    else
                        return false;
                }
                else
                {
                    if (now > TOD_start && now < TOD_end)
                        return true;
                    else
                        return false;
                }

            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public TimeSpan DespawnTime
        {
            get { return m_DespawnTime; }
            set { m_DespawnTime = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public TimeSpan Duration
        {
            get { return m_Duration; }
            set
            {
                m_Duration = value;
                InvalidateProperties();
            }
        }
        [CommandProperty(AccessLevel.GameMaster)]
        public TimeSpan DurationOver
        {
            get
            {
                if (m_durActivated)
                    return m_DurEnd - DateTime.Now;
                else
                    return TimeSpan.FromSeconds(0);
            }
            set
            {
                DoTimer2(value);
            }
        }
        // proximity range parameter
        [CommandProperty(AccessLevel.GameMaster)]
        public int ProximityRange
        {
            get { return m_ProximityRange; }
            set
            {
                m_ProximityRange = value;
                InvalidateProperties();
            }
        }


        // proximity range activated?
        [CommandProperty(AccessLevel.GameMaster)]
        public bool ProximityActivated
        {
            get { return m_proximityActivated; }
            set
            {

                if (AllowTriggering)
                {
                    ActivateTrigger();
                }

                m_proximityActivated = value;

            }
        }

        // proximity trigger sound parameter
        [CommandProperty(AccessLevel.GameMaster)]
        public int ProximitySound
        {
            get { return m_ProximityTriggerSound; }
            set { m_ProximityTriggerSound = value; }
        }

        // proximity trigger message parameter
        [CommandProperty(AccessLevel.GameMaster)]
        public string ProximityMsg
        {
            get { return m_ProximityTriggerMessage; }
            set { m_ProximityTriggerMessage = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public string SpeechTrigger
        {
            get { return m_SpeechTrigger; }
            set { m_SpeechTrigger = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public string SkillTrigger
        {
            get { return m_SkillTrigger; }
            set
            {

                SkillName news = XmlSpawnerSkillCheck.RegisteredSkill.Invalid;
                double minval = -1;
                double maxval = -1;
                int successval = 3;  // either success or failure will trigger

                if (value != null)
                {
                    // try parsing the skill trigger string for min and maxval
                    // the string can take the form "skill,[+/-][,minval,maxval]"

                    string[] arglist = BaseXmlSpawner.ParseString(value, 4, ",");

                    if (arglist.Length == 2 || arglist.Length == 4)
                    {
                        if (arglist[1] == "+")
                        {
                            successval = 1;     // trigger on success only
                        }
                        else
                            if (arglist[1] == "-")
                            {
                                successval = 2;     // trigger on failure only
                            }
                    }

                    if (arglist.Length == 3)
                    {
                        successval = 3;
                        try
                        {
                            minval = double.Parse(arglist[1]);
                            maxval = double.Parse(arglist[2]);
                        }
                        catch { }
                    }
                    else
                        if (arglist.Length == 4)
                        {
                            try
                            {
                                minval = double.Parse(arglist[2]);
                                maxval = double.Parse(arglist[3]);
                            }
                            catch { }
                        }
                    try
                    {
                        news = (SkillName)Enum.Parse(typeof(SkillName), arglist[0], true);
                    }
                    catch { }
                }

                // unregister the previous skill if it was assigned
                if (m_SkillTrigger != null)
                {
                    XmlSpawnerSkillCheck.UnRegisterSkillTrigger(this, m_SkillTriggerName, this.Map, false);
                }

                // if the skill trigger was valid then register it
                if (news != XmlSpawnerSkillCheck.RegisteredSkill.Invalid)
                {
                    XmlSpawnerSkillCheck.RegisterSkillTrigger(this, news, this.Map);
                    m_SkillTrigger = value;
                    m_SkillTriggerName = news;
                    m_SkillTriggerMin = minval;
                    m_SkillTriggerMax = maxval;
                    m_SkillTriggerSuccess = successval;
                }
                else
                {
                    m_SkillTrigger = null;
                    m_SkillTriggerName = XmlSpawnerSkillCheck.RegisteredSkill.Invalid;
                    m_SkillTriggerMin = -1;
                    m_SkillTriggerMax = -1;
                }
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public TimeSpan NextSpawn
        {
            get
            {
                if (m_Running)
                    return m_End - DateTime.Now;
                else
                    return TimeSpan.FromSeconds(0);
            }
            set
            {
                Start();
                DoTimer(value);
                //InvalidateProperties();
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool SpawnOnTrigger
        {
            get { return m_SpawnOnTrigger; }
            set { m_SpawnOnTrigger = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool Group
        {
            get { return m_Group; }
            set { m_Group = value; InvalidateProperties(); }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public string GumpState
        {
            get { return m_GumpState; }
            set { m_GumpState = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public int SequentialSpawn
        {
            get { return m_SequentialSpawning; }
            set { m_SequentialSpawning = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public TimeSpan NextSeqReset
        {
            get
            {
                if (m_Running && ((m_SeqEnd - DateTime.Now) > TimeSpan.Zero))
                    return m_SeqEnd - DateTime.Now;
                else
                    return TimeSpan.FromSeconds(0);
            }
            set
            {
                m_SeqEnd = DateTime.Now + value;
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public AccessLevel TriggerAccessLevel
        {
            get { return m_TriggerAccessLevel; }
            set { m_TriggerAccessLevel = value; }
        }


        [CommandProperty(AccessLevel.GameMaster)]
        public bool DoRespawn
        {
            get { return 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 == true && !inrespawn)
                {
                    Respawn();
                }
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool DoReset
        {
            get { return false; }
            set { if (value == true) Reset(); }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool AllowGhostTrig
        {
            get { return m_AllowGhostTriggering; }
            set { m_AllowGhostTriggering = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool AllowNPCTrig
        {
            get { return m_AllowNPCTriggering; }
            set { m_AllowNPCTriggering = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public string ConfigFile
        {
            get { return m_ConfigFile; }
            set { m_ConfigFile = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool LoadConfig
        {
            get { return false; }
            set { if (value == true) LoadXmlConfig(ConfigFile); }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public Mobile TriggerMob
        {
            get { return m_mob_who_triggered; }
            set { m_mob_who_triggered = value; }
        }


        [CommandProperty(AccessLevel.GameMaster)]
        public string FirstModifiedBy
        {
            get { return m_FirstModifiedBy; }
            set
            {
                m_FirstModifiedBy = value;
                m_FirstModified = DateTime.Now;
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public string LastModifiedBy
        {
            get { return m_LastModifiedBy; }
            set
            {
                m_LastModifiedBy = value;
                m_LastModified = DateTime.Now;
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool SmartSpawning
        {
            get { return 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; 
            }
        }

        #endregion

        #region ISpawner interface support

        public bool UnlinkOnTaming { get { return true; } }
		public Point3D HomeLocation { get { return this.Location; } }
        public int Range { get { return HomeRange; } }

		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);
                        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)o).Spawner = this;
                    }
                    else if (o is Mobile)
                    {
                        ((Mobile)o).Spawner = this;
                    }
                }
            }
        }

        #endregion
        #region Method Overrides

        public override void OnAfterDuped(Item newItem)
        {
            // automatically turn off duped spawners
            ((XmlSpawner)newItem).Running = false;
        }

        public override void OnMapChange()
        {
            base.OnMapChange();

            // unregister the skill trigger on the previous map
            XmlSpawnerSkillCheck.UnRegisterSkillTrigger(this, m_SkillTriggerName, currentmap, false);

            // register the skill trigger on the new current map
            XmlSpawnerSkillCheck.RegisterSkillTrigger(this, m_SkillTriggerName, this.Map);

            currentmap = this.Map;

            // reset the sector list for smart spawning
            ResetSectorList();
        }

        public override void OnDoubleClick(Mobile from)
        {
            if (from == null || from.Deleted || from.AccessLevel < AccessLevel.GameMaster || (m_SpawnerGump != null && SomeOneHasGumpOpen)) return;

            // flag the first person to open the spawner as the placer
            if (FirstModifiedBy == null) FirstModifiedBy = from.Name;

            LastModifiedBy = from.Name;

            // clear any text entry books that might still be around
            DeleteTextEntryBook();

            int x = 0;
            int y = 0;
            // read the text entries for default values
            Account acct = from.Account as Account;
            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(ObjectPropertyList list)
        {
            base.GetProperties(list);

            if (m_Running)
            {
                list.Add(1060742); // active
            }
            else
            {
                list.Add(1060743); // 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{0}", m_Group); // ~1_val~: ~2_val~
                nlist_items--;
            }

            if (m_Team != 0)
            {
                list.Add(1060658 + 6 - nlist_items, "team\t{0}", m_Team); // ~1_val~: ~2_val~
                nlist_items--;
            }

            list.Add(1060658 + 6 - nlist_items, "speed\t{0} to {1}", m_MinDelay, 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{0}", 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{0}", m_ProximityRange);
                nlist_items--;
            }

            if (m_SpawnObjects != null)
                for (int i = 0; i < nlist_items && i < m_SpawnObjects.Count; ++i)
                {
                    string typename = ((SpawnObject)m_SpawnObjects[i]).TypeName;
                    if (typename != null && typename.Length > 20)
                    {
                        typename = typename.Substring(0, 20);
                    }

                    list.Add(1060658 + (6 - nlist_items) + i, " {0}\t{1}", typename, ((SpawnObject)m_SpawnObjects[i]).SpawnedObjects.Count);
                }


        }

        public override void OnSingleClick(Mobile from)
        {
            LabelTo(from, "XmlSpawner");
            LabelTo(from, Name + (m_Running == true ? " [On]" : " [Off]"));
        }


        public override void OnDelete()
        {
            base.OnDelete();

            if (ShowBounds == true)
                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();

            // unregister all triggerskills that might have been added
            XmlSpawnerSkillCheck.UnRegisterSkillTrigger(this, SkillName.Alchemy, this.Map, true);
        }

        static bool IgnoreLocationChange = false;
        public override void OnLocationChange(Point3D oldLocation)
        {
            if (IgnoreLocationChange)
            {
                IgnoreLocationChange = false;
                return;
            }


            // calculate the positional shift
            if (oldLocation.X > 0 && oldLocation.Y > 0)
            {
                int diffx = this.X - oldLocation.X;
                int diffy = this.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 = this.X - (m_Width / 2);
                m_Y = this.Y - (m_Height / 2);
            }

            // reset the sector list for smart spawning
            ResetSectorList();

            // Check if the spawner is showing its bounds
            if (ShowBounds == true)
            {
                ShowBounds = false;
                ShowBounds = true;
            }
        }
        #endregion

        #region Gump support

        public bool SomeOneHasGumpOpen
        {
            get
            {
                // go through all online mobiles and see if any have xmlspawner gumps open
                List<NetState> states = NetState.Instances;

                for (int i = 0; i < states.Count; ++i)
                {
                    Mobile m = ((NetState)states[i]).Mobile;

                    if (m != null && m.HasGump(typeof(XmlSpawnerGump)))
                    {
                        return true;
                    }
                }

                return false;
            }
        }


        public static void SpawnerGumpCallback(Mobile from, object invoker, string response)
        {
            // assign the response to the gumpstate
            if (invoker is XmlSpawner)
            {
                XmlSpawner xs = (XmlSpawner)invoker;
                xs.GumpState = response;
            }
        }

        public void DeleteTextEntryBook()
        {
            if (m_TextEntryBook != null)
            {
                foreach (Item s in m_TextEntryBook)
                    s.Delete();

                m_TextEntryBook = null;
            }
        }

        #endregion

        #region Utility Methods

        private static bool IsConstructable(ConstructorInfo ctor)
        {
            return ctor.IsDefined(typeof(ConstructableAttribute), false);
        }

        public static int ConvertToInt(string value)
        {
            if (value.StartsWith("0x"))
            {
                return Convert.ToInt32(value.Substring(2), 16);
            }
            else
            {
                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)
            {
                loc = ((IEntity)attachedto).Location;
                map = ((IEntity)attachedto).Map;
            }

            if (action == null || action.Length <= 0 || attachedto == null || map == null) return;

            string status_str = null;
            Server.Mobiles.XmlSpawner.SpawnObject TheSpawn = new Server.Mobiles.XmlSpawner.SpawnObject(null, 0);

            TheSpawn.TypeName = action;
            string substitutedtypeName = BaseXmlSpawner.ApplySubstitution(null, attachedto, trigmob, action);
            string typeName = BaseXmlSpawner.ParseObjectType(substitutedtypeName);

            if (BaseXmlSpawner.IsTypeOrItemKeyword(typeName))
            {
                BaseXmlSpawner.SpawnTypeKeyword(attachedto, TheSpawn, typeName, substitutedtypeName, true, trigmob, loc, map, out status_str);
            }
            else
            {
                // its a regular type descriptor so find out what it is
                Type type = SpawnerType.GetType(typeName);
                try
                {
                    string[] arglist = BaseXmlSpawner.ParseString(substitutedtypeName, 3, "/");
                    object o = Server.Mobiles.XmlSpawner.CreateObject(type, arglist[0]);

                    if (o == null)
                    {
                        status_str = "invalid type specification: " + arglist[0];
                    }
                    else
                        if (o is Mobile)
                        {
                            Mobile m = (Mobile)o;
                            if (m is BaseCreature)
                            {
                                BaseCreature c = (BaseCreature)m;
                                c.Home = loc; // Spawners location is the home point
                            }

                            m.Location = loc;
                            m.Map = map;

                            BaseXmlSpawner.ApplyObjectStringProperties(null, substitutedtypeName, m, trigmob, attachedto, out status_str);
                        }
                        else
                            if (o is Item)
                            {
                                Item item = (Item)o;
                                BaseXmlSpawner.AddSpawnItem(null, attachedto, TheSpawn, item, loc, map, trigmob, false, substitutedtypeName, out status_str);
                            }
                }
                catch { }
            }
        }

        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
            if (GlobalSectorTable[s.Owner.MapID].Contains(s))
            {
                ArrayList spawnerlist = (ArrayList)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;

            //IsInactivated = 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 (System.IO.File.Exists(filename) == true)
            {
                FileStream fs = null;
                try
                {
                    fs = File.Open(filename, FileMode.Open, FileAccess.Read);
                }
                catch { }

                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 != null && ds.Tables.Count > 0)
                {
                    if (ds.Tables[XmlTableName] != null && ds.Tables[XmlTableName].Rows.Count > 0)
                    {
                        foreach (DataRow dr in ds.Tables[XmlTableName].Rows)
                        {
                            bool valid_entry;
                            string strEntry = null;
                            bool boolEntry = true;
                            double doubleEntry = 0;
                            int intEntry = 0;

                            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) { if (delayinsec) m_MinDelay = TimeSpan.FromSeconds(doubleEntry); else m_MinDelay = TimeSpan.FromMinutes(doubleEntry); }

                            valid_entry = true;
                            try { doubleEntry = double.Parse((string)dr["MaxDelay"]); }
                            catch { valid_entry = false; }
                            if (valid_entry) { if (delayinsec) m_MaxDelay = TimeSpan.FromSeconds(doubleEntry); else m_MaxDelay = 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 { }
                            }

                            // 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 ArrayList();

                                // 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 Skills");
            Console.WriteLine("Felucca");
            for (int i = 0; i < XmlSpawnerSkillCheck.RegisteredSkill.MaxSkills + 1; i++)
            {

                if (XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Felucca).Count > 0)
                    Console.WriteLine("\t{0} : {1}", (SkillName)i, XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Felucca).Count);

                count += XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Felucca).Count;
            }
            Console.WriteLine("Trammel");
            for (int i = 0; i < XmlSpawnerSkillCheck.RegisteredSkill.MaxSkills + 1; i++)
            {

                if (XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Trammel).Count > 0)
                    Console.WriteLine("\t{0} : {1}", (SkillName)i, XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Trammel).Count);

                count += XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Trammel).Count;
            }
            Console.WriteLine("Ilshenar");
            for (int i = 0; i < XmlSpawnerSkillCheck.RegisteredSkill.MaxSkills + 1; i++)
            {

                if (XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Ilshenar).Count > 0)
                    Console.WriteLine("\t{0} : {1}", (SkillName)i, XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Ilshenar).Count);

                count += XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Ilshenar).Count;
            }
            Console.WriteLine("Malas");
            for (int i = 0; i < XmlSpawnerSkillCheck.RegisteredSkill.MaxSkills + 1; i++)
            {

                if (XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Malas).Count > 0)
                    Console.WriteLine("\t{0} : {1}", (SkillName)i, XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Malas).Count);

                count += XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Malas).Count;
            }

            Console.WriteLine("Tokuno");
            for (int i = 0; i < XmlSpawnerSkillCheck.RegisteredSkill.MaxSkills + 1; i++)
            {

                if (XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Tokuno).Count > 0)
                    Console.WriteLine("\t{0} : {1}", (SkillName)i, XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Tokuno).Count);

                count += XmlSpawnerSkillCheck.RegisteredSkill.TriggerList((SkillName)i, Map.Tokuno).Count;
            }

            Console.WriteLine("Total = {0}", count);
        }

        #endregion

        #region Trace support

#if(TRACE)

string setname1 = _traceName[1] = "XmlFind";
string setname2 = _traceName[2] = "HasSector";

string setname4 = _traceName[4] = "AttachSpeech";
string setname5 = _traceName[5] = "HasHold";


string setname8 = _traceName[8] = "OnTick";
string setname9 = _traceName[9] = "Defrag";
string setname10 = _traceName[10] = "Respawn";
string setname11 = _traceName[11] = "SetProp";
string setname12 = _traceName[12] = "AttachMovement";
string setname13 = _traceName[13] = "ActiveSector";

string setname15 = _traceName[15] = "DistroTick";
string setname16 = _traceName[16] = "GetScaledFaction";
string setname17 = _traceName[17] = "FactionOnKill";
string setname18 = _traceName[18] = "CheckAcquire";



private const int MaxTraces = 20;
private static 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.Now;
private static double _startProcessTime  = 0;

public static void _TraceStart(int index)
{
	   if(index < MaxTraces){
			_traceStart[index] =  DateTime.Now;
			//_traceStart[index] =  Process.GetCurrentProcess().UserProcessorTime;
		}
}
public static void _TraceEnd(int index)
{
	   if(index < MaxTraces){
				XmlSpawner._traceTotal[index] = XmlSpawner._traceTotal[index].Add(DateTime.Now - _traceStart[index]);
				//XmlSpawner._traceTotal[index] = XmlSpawner._traceTotal[index].Add(Process.GetCurrentProcess().UserProcessorTime - _traceStart[index]);
				XmlSpawner._traceCount[index]++;
	   }
}
#else
        public static void _TraceStart(int index) { }
        public static void _TraceEnd(int index) { }
#endif

        #endregion

        #region Trigger Methods

        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
        {
            get
            {
                return m_Running && !m_refractActivated && TODInRange && CanSpawn;
            }
        }

        private void ActivateTrigger()
        {

            // reset the timer
            DoTimer();

            // 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;

            // reset skill triggering if it was set
            m_skillTriggerActivated = false;

            // reset external triggering if it was set
            //this.m_ExternalTrigger = false;
        }

        public void CheckTriggers(Mobile m, Skill s, bool hasproximity)
        {

            // only proximity trigger when no spawns have already been triggered
            if (AllowTriggering && !m_proximityActivated)
            {
                bool needs_item_trigger = false;
                bool needs_speech_trigger = false;
                bool needs_skill_trigger = false;
                bool needs_object_trigger = false;
                bool needs_mob_trigger = false;
                bool needs_player_trigger = false;
                bool needs_noitem_trigger = false;
                bool has_object_trigger = false;
                bool has_mob_trigger = false;
                bool has_player_trigger = false;
                bool has_item_trigger = false;
                bool has_noitem_trigger = true; // assume the player doesnt have the trigger-blocking item until it is found by search

                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 (m_SpeechTrigger != null && m_SpeechTrigger.Length > 0)
                {
                    needs_speech_trigger = true;
                }
                // check to see if we have to continue
                if (needs_speech_trigger && !m_speechTriggerActivated) return;

                // if skill triggering is set then test for successful activation
                if (m_SkillTrigger != null && m_SkillTrigger.Length > 0)
                {
                    needs_skill_trigger = true;
                }
                // check to see if we have to continue
                if (needs_skill_trigger && !m_skillTriggerActivated) return;

                // if item property triggering is set then test for the property value
                //
                if (m_ObjectPropertyName != null && m_ObjectPropertyName.Length > 0)
                {
                    needs_object_trigger = true;
                    string status_str;

                    if (BaseXmlSpawner.TestItemProperty(this, m_ObjectPropertyItem, m_ObjectPropertyName, null, out status_str))
                    {
                        has_object_trigger = true;
                    }
                    else
                        has_object_trigger = false;
                    if (status_str != null && status_str.Length > 0)
                    {
                        this.status_str = status_str;
                    }
                }

                // check to see if we have to continue
                if (needs_object_trigger && !has_object_trigger) return;

                // if player property triggering is set then look for the mob and test properties
                if (m_PlayerPropertyName != null && m_PlayerPropertyName.Length > 0)
                {
                    needs_player_trigger = true;
                    string status_str;

                    if (BaseXmlSpawner.TestMobProperty(this, m, m_PlayerPropertyName, null, out status_str))
                    {
                        has_player_trigger = true;
                    }
                    else
                        has_player_trigger = false;
                    if (status_str != null && status_str.Length > 0)
                    {
                        this.status_str = status_str;
                    }
                }

                // check to see if we have to continue
                if (needs_player_trigger && !has_player_trigger) return;

                // if mob property triggering is set then look for the mob and test properties
                if (m_MobPropertyName != null && m_MobPropertyName.Length > 0 &&
                    m_MobTriggerName != null && m_MobTriggerName.Length > 0)
                {
                    needs_mob_trigger = true;

                    string status_str;

                    if (BaseXmlSpawner.TestMobProperty(this, MobTriggerId, m_MobPropertyName, null, out status_str))
                    {
                        has_mob_trigger = true;
                    }
                    else
                        has_mob_trigger = false;

                    if (status_str != null && status_str.Length > 0)
                    {
                        this.status_str = status_str;
                    }
                }

                // check to see if we have to continue
                if (needs_mob_trigger && !has_mob_trigger) return;

                // if player-carried item triggering is set then test for the presence of an item on the player an in their pack
                if (m_ItemTriggerName != null && m_ItemTriggerName.Length > 0)
                {
                    //enable_triggering = false;
                    needs_item_trigger = true;

                    has_item_trigger = BaseXmlSpawner.CheckForCarried(m, m_ItemTriggerName);
                }
                // check to see if we have to continue
                if (needs_item_trigger && !has_item_trigger) return;

                // if player-carried noitem triggering is set then test for the presence of an item in the players pack that should block triggering
                if (m_NoItemTriggerName != null && m_NoItemTriggerName.Length > 0)
                {
                    needs_noitem_trigger = true;

                    has_noitem_trigger = BaseXmlSpawner.CheckForNotCarried(m, m_NoItemTriggerName);
                }
                // check to see if we have to continue
                if (needs_noitem_trigger && !has_noitem_trigger) return;

                // if this was called without being proximity triggered then check to see that the non-movement triggers were enabled.
                if (!hasproximity && !needs_object_trigger && !needs_mob_trigger && !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 (m_ProximityTriggerMessage != null && m_ProximityTriggerMessage.Length > 0 && m != null && !m.Deleted)
                        m.PublicOverheadMessage(MessageType.Regular, 0x3B2, true, 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;

                    // keep track of the skill that triggered this
                    if (s != null)
                    {
                        m_skill_that_triggered = s.SkillName;
                    }
                    else
                    {
                        m_skill_that_triggered = XmlSpawnerSkillCheck.RegisteredSkill.Invalid;
                    }


                }
                else
                {
                    m_skipped = true;
                    // reset speech triggering if it was set

                    m_speechTriggerActivated = false;

                    // reset skill triggering if it was set
                    m_skillTriggerActivated = false;
                    // reset external triggering if it was set
                    //this.m_ExternalTrigger = false;
                }
            }
        }

        public bool HandlesOnSkillUse { get { return (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, this.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 { get { return (m_Running && m_SpeechTrigger != null && m_SpeechTrigger.Length > 0); } }

        public override void OnSpeech(SpeechEventArgs e)
        {
            if ( /*!e.Handled && */m_Running && m_ProximityRange >= 0 && ValidPlayerTrig(e.Mobile) && CanSpawn && !m_refractActivated && TODInRange)
            {
                m_speechTriggerActivated = false;

                if (!Utility.InRange(e.Mobile.Location, this.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
        {
            get
            {
                return (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 ArrayList();
            }

            // 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 XmlSpawner m_Spawner;

            public MovementTimer(XmlSpawner spawner, TimeSpan delay)
                : base(delay)
            {
                Priority = TimerPriority.OneSecond;
                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;
                        int speed = 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;

                            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 ((this.Parent == null) && Utility.InRange(m.Location, this.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);

                    // check the triggers in the OnTick for the list handler instead of here
                }
                else
                {
                    // clear any speech triggering
                    m_speechTriggerActivated = false;

                    // clear any skill triggering
                    m_skillTriggerActivated = false;
                }
            }
            base.OnMovement(m, oldLocation);
        }
        #endregion

        #region Initialization

        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 "XmlConfigsDir":
                    XmlConfigsDir = value;
                    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 "JournalNotifyColor":
                    XmlQuestHolder.JournalNotifyColor = ConvertToInt(value);
                    break;
                case "JournalEchoColor":
                    XmlQuestHolder.JournalEchoColor = ConvertToInt(value);
                    break;
                case "BlockKeyword":
                    {
                        // parse the keyword list and remove them from the keyword hashtables
                        string[] keywordlist = value.Split(',');

                        if (keywordlist != null && 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, new TimerStateCallback(DelayedAssignSettings), new object[] { argname, value });
                    break;
                default:
                    return false;
            }

            return true;
        }

        private static void DelayedAssignSettings(object state)
        {
            object[] args = (object[])state;
            string argname = (string)args[0];
            string value = (string)args[1];
            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 != null && 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 != null && keywordlist.Length > 0)
                        {
                            for (int i = 0; i < keywordlist.Length; i++)
                            {
                                string[] namelist = keywordlist[i].Split(':');
                                if (namelist != null && 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<BaseCommandImplementor> 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 { }
                                                            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);
                        }
                    }

                }
                if (nsettings > 0)
                {
                    Console.WriteLine("{0} settings processed", nsettings);
                }
            }
        }

        public static void Initialize()
        {
            XmlSpawner.LoadSettings(new XmlSpawner.AssignSettingsHandler(AssignSettings), "XmlSpawner");

            // initialize the default waypoint name
            WayPoint tmpwaypoint = new WayPoint();
            if (tmpwaypoint != null)
            {
                defwaypointname = tmpwaypoint.Name;
                tmpwaypoint.Delete();
            }
            // 2004.02.08 :: Omega Red
            // initialize m_Region fields after world load (now, regions are loaded)
            // Now this gets handled in OnTick

            int count = 0;
            int regional = 0;
            //int timercount=0;

            foreach (Item item in World.Items.Values)
            {
                if (item is XmlSpawner)
                {
                    count++;
                    XmlSpawner spawner = ((XmlSpawner)item);

                    if (spawner.RegionName != null && spawner.RegionName != string.Empty)
                    {
                        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;
                    /*
                                        if(spawner.SmartSpawning && spawner.IsInactivated && !spawner.UseSectorActivate)
                                        {
                                            spawner.DoSectorTimer(TimeSpan.FromSeconds(1));
                                            timercount++;
                                        }
                    */
                    // add in the totalitem mod to keep them from adding to container counts
                    //spawner.TotalItems = -1;
                    //spawner.UpdateTotal(spawner, TotalType.Items, -1);

                    spawner.RestoreISpawner();
                }
            }

            // start the global smartspawning timer
            if (SmartSpawningSystemEnabled)
            {
                DoGlobalSectorTimer(TimeSpan.FromSeconds(1));
            }

            // standard commands
            CommandSystem.Register("XmlSpawnerShowAll", AccessLevel.Administrator, new CommandEventHandler(ShowSpawnPoints_OnCommand));
            CommandSystem.Register("XmlSpawnerHideAll", AccessLevel.Administrator, new CommandEventHandler(HideSpawnPoints_OnCommand));
            CommandSystem.Register("XmlSpawnerWipe", AccessLevel.Administrator, new CommandEventHandler(Wipe_OnCommand));
            CommandSystem.Register("XmlSpawnerWipeAll", AccessLevel.Administrator, new CommandEventHandler(WipeAll_OnCommand));
            CommandSystem.Register("XmlSpawnerLoad", DiskAccessLevel, new CommandEventHandler(Load_OnCommand));
            CommandSystem.Register("XmlSpawnerSave", DiskAccessLevel, new CommandEventHandler(Save_OnCommand));
            CommandSystem.Register("XmlSpawnerSaveAll", DiskAccessLevel, new CommandEventHandler(SaveAll_OnCommand));
            //added respawn commands
            CommandSystem.Register("XmlSpawnerRespawn", AccessLevel.GameMaster, new CommandEventHandler(Respawn_OnCommand));
            CommandSystem.Register("XmlSpawnerRespawnAll", AccessLevel.GameMaster, new CommandEventHandler(RespawnAll_OnCommand));

            // ok, I'm lazy. I dont like all that typing, so these are two aliases for the longer commands
            CommandSystem.Register("XmlShow", AccessLevel.Administrator, new CommandEventHandler(ShowSpawnPoints_OnCommand));
            CommandSystem.Register("XmlHide", AccessLevel.Administrator, new CommandEventHandler(HideSpawnPoints_OnCommand));

            CommandSystem.Register("XmlHome", AccessLevel.GameMaster, new CommandEventHandler(XmlHome_OnCommand));
            CommandSystem.Register("XmlUnLoad", DiskAccessLevel, new CommandEventHandler(UnLoad_OnCommand));
            CommandSystem.Register("XmlSpawnerUnLoad", DiskAccessLevel, new CommandEventHandler(UnLoad_OnCommand));
            CommandSystem.Register("XmlLoad", DiskAccessLevel, new CommandEventHandler(Load_OnCommand));
            CommandSystem.Register("XmlLoadHere", DiskAccessLevel, new CommandEventHandler(LoadHere_OnCommand));
            CommandSystem.Register("XmlNewLoad", DiskAccessLevel, new CommandEventHandler(NewLoad_OnCommand));
            CommandSystem.Register("XmlNewLoadHere", DiskAccessLevel, new CommandEventHandler(NewLoadHere_OnCommand));
            CommandSystem.Register("XmlSave", DiskAccessLevel, new CommandEventHandler(Save_OnCommand));
            CommandSystem.Register("XmlSaveAll", DiskAccessLevel, new CommandEventHandler(SaveAll_OnCommand));
            CommandSystem.Register("XmlSaveOld", DiskAccessLevel, new CommandEventHandler(SaveOld_OnCommand));
            CommandSystem.Register("XmlImportSpawners", DiskAccessLevel, new CommandEventHandler(XmlImportSpawners_OnCommand));
            CommandSystem.Register("XmlImportMSF", DiskAccessLevel, new CommandEventHandler(XmlImportMSF_OnCommand));
            CommandSystem.Register("XmlImportMap", DiskAccessLevel, new CommandEventHandler(XmlImportMap_OnCommand));
            CommandSystem.Register("XmlDefaults", AccessLevel.Administrator, new CommandEventHandler(XmlDefaults_OnCommand));
            CommandSystem.Register("XmlGet", AccessLevel.GameMaster, new CommandEventHandler(XmlGetValue_OnCommand));
            TargetCommands.Register(new XmlSetCommand());
            //CommandSystem.Register( "XmlSet", AccessLevel.GameMaster, new CommandEventHandler( XmlSetValue_OnCommand ) );
            CommandSystem.Register("OptimalSmartSpawning", AccessLevel.Administrator, new CommandEventHandler(OptimalSmartSpawning_OnCommand));
            CommandSystem.Register("SmartStat", AccessLevel.GameMaster, new CommandEventHandler(SmartStat_OnCommand));
            CommandSystem.Register("XmlGo", AccessLevel.GameMaster, new CommandEventHandler(SpawnEditorGo_OnCommand));
            //CommandSystem.Register( "TagList", AccessLevel.Administrator, new CommandEventHandler( ShowTagList_OnCommand ) );

#if(TRACE)
			CommandSystem.Register( "XmlMake", AccessLevel.Administrator, new CommandEventHandler( XmlMake_OnCommand ) );
			CommandSystem.Register( "XmlTrace", AccessLevel.Administrator, new CommandEventHandler( XmlTrace_OnCommand ) );
			CommandSystem.Register( "XmlResetTrace", AccessLevel.Administrator, new CommandEventHandler( XmlResetTrace_OnCommand ) );
#endif

        }

        #endregion

        #region Commands

        [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 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 string[] { "XmlSet" };
                ObjectTypes = ObjectTypes.Both;
                Usage = "XmlSet <propertyName> <value>";
                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 <propertyName> <value>");
                }
            }
        }


        [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 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)
                    ((XmlSpawner)targeted).ShowTagList((XmlSpawner)targeted);

            }
        }

        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 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)
                {
                    if (m_e.GetString(0) == "status")
                    {
                        ((XmlSpawner)targeted).ReportStatus();
                        return;
                    }
                }


                if (targeted is Mobile)
                {
                    spawner = ((Mobile)targeted).Spawner as XmlSpawner;
                }
                else
                    if (targeted is Item)
                    {
                        spawner = ((Item)targeted).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)o).Location = new Point3D(spawner.Location);
                                            ((Item)o).Map = spawner.Map;
                                        }
                                        if (o is Mobile)
                                        {
                                            ((Mobile)o).Location = new Point3D(spawner.Location);
                                            ((Mobile)o).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 (filePath == null || filePath.Length < 1) return;

            using (StreamWriter op = new StreamWriter(filePath))
            {
                if (op == null)
                {
                    m.SendMessage("unable to open file {0}", filePath);
                }
                XmlTextWriter xml = new XmlTextWriter(op);

                xml.Formatting = Formatting.Indented;
                xml.IndentChar = '\t';
                xml.Indentation = 1;

                xml.WriteStartDocument(true);

                xml.WriteStartElement("XmlDefaults");

                xml.WriteStartElement("defProximityRange");
                xml.WriteString(XmlSpawner.defProximityRange.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defTriggerProbability");
                xml.WriteString(XmlSpawner.defTriggerProbability.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defProximityTriggerSound");
                xml.WriteString(XmlSpawner.defProximityTriggerSound.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defMinRefractory");
                xml.WriteString(XmlSpawner.defMinRefractory.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defMaxRefractory");
                xml.WriteString(XmlSpawner.defMaxRefractory.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defTODStart");
                xml.WriteString(XmlSpawner.defTODStart.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defTODEnd");
                xml.WriteString(XmlSpawner.defTODEnd.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defStackAmount");
                xml.WriteString(XmlSpawner.defAmount.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defDuration");
                xml.WriteString(XmlSpawner.defDuration.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defIsGroup");
                xml.WriteString(XmlSpawner.defIsGroup.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defTeam");
                xml.WriteString(XmlSpawner.defTeam.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defRelativeHome");
                xml.WriteString(XmlSpawner.defRelativeHome.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defSpawnRange");
                xml.WriteString(XmlSpawner.defSpawnRange.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defHomeRange");
                xml.WriteString(XmlSpawner.defHomeRange.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defMinDelay");
                xml.WriteString(XmlSpawner.defMinDelay.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defMaxDelay");
                xml.WriteString(XmlSpawner.defMaxDelay.ToString());
                xml.WriteEndElement();
                xml.WriteStartElement("defTODMode");
                xml.WriteString(XmlSpawner.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 (filePath != null && filePath.Length >= 1)
            {

                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 { XmlSpawner.defProximityRange = int.Parse(node["defProximityRange"].InnerText); }
            catch { }
            try { XmlSpawner.defTriggerProbability = double.Parse(node["defTriggerProbability"].InnerText); }
            catch { }
            try { XmlSpawner.defProximityTriggerSound = int.Parse(node["defProximityTriggerSound"].InnerText); }
            catch { }
            try { XmlSpawner.defMinRefractory = TimeSpan.Parse(node["defMinRefractory"].InnerText); }
            catch { }
            try { XmlSpawner.defMaxRefractory = TimeSpan.Parse(node["defMaxRefractory"].InnerText); }
            catch { }
            try { XmlSpawner.defTODStart = TimeSpan.Parse(node["defTODStart"].InnerText); }
            catch { }
            try { XmlSpawner.defTODEnd = TimeSpan.Parse(node["defTODEnd"].InnerText); }
            catch { }
            try { XmlSpawner.defAmount = int.Parse(node["defStackAmount"].InnerText); }
            catch { }
            try { XmlSpawner.defDuration = TimeSpan.Parse(node["defDuration"].InnerText); }
            catch { }
            try { XmlSpawner.defIsGroup = bool.Parse(node["defIsGroup"].InnerText); }
            catch { }
            try { XmlSpawner.defTeam = int.Parse(node["defTeam"].InnerText); }
            catch { }
            try { XmlSpawner.defRelativeHome = bool.Parse(node["defRelativeHome"].InnerText); }
            catch { }
            try { XmlSpawner.defSpawnRange = int.Parse(node["defSpawnRange"].InnerText); }
            catch { }
            try { XmlSpawner.defHomeRange = int.Parse(node["defHomeRange"].InnerText); }
            catch { }
            try { XmlSpawner.defMinDelay = TimeSpan.Parse(node["defMinDelay"].InnerText); }
            catch { }
            try { XmlSpawner.defMaxDelay = TimeSpan.Parse(node["defMaxDelay"].InnerText); }
            catch { }
            int todmode = 0;
            try { todmode = int.Parse(node["defTODMode"].InnerText); }
            catch { }
            switch (todmode)
            {
                case (int)TODModeType.Realtime:
                    XmlSpawner.defTODMode = TODModeType.Realtime;
                    break;
                case (int)TODModeType.Gametime:
                    XmlSpawner.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
                            // try to set the property
                            if (e.Arguments[0].ToLower() == "maxdelay")
                            {
                                try
                                {
                                    XmlSpawner.defMaxDelay = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
                                    m.SendMessage("MaxDelay = {0}", XmlSpawner.defMaxDelay);
                                }
                                catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                            }
                            else
                                if (e.Arguments[0].ToLower() == "mindelay")
                                {
                                    try
                                    {
                                        XmlSpawner.defMinDelay = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
                                        m.SendMessage("MinDelay = {0}", XmlSpawner.defMinDelay);
                                    }
                                    catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                }
                                else
                                    if (e.Arguments[0].ToLower() == "spawnrange")
                                    {
                                        try
                                        {
                                            XmlSpawner.defSpawnRange = Convert.ToInt32(e.Arguments[1]);
                                            m.SendMessage("SpawnRange = {0}", XmlSpawner.defSpawnRange);
                                        }
                                        catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                    }
                                    else
                                        if (e.Arguments[0].ToLower() == "homerange")
                                        {
                                            try
                                            {
                                                XmlSpawner.defHomeRange = Convert.ToInt32(e.Arguments[1]);
                                                m.SendMessage("HomeRange = {0}", XmlSpawner.defHomeRange);
                                            }
                                            catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                        }
                                        else
                                            if (e.Arguments[0].ToLower() == "relativehome")
                                            {
                                                try
                                                {
                                                    XmlSpawner.defRelativeHome = Convert.ToBoolean(e.Arguments[1]);
                                                    m.SendMessage("RelativeHome = {0}", XmlSpawner.defRelativeHome);
                                                }
                                                catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                            }
                                            else
                                                if (e.Arguments[0].ToLower() == "proximitytriggersound")
                                                {
                                                    try
                                                    {
                                                        XmlSpawner.defProximityTriggerSound = Convert.ToInt32(e.Arguments[1]);
                                                        m.SendMessage("ProximityTriggerSound = {0}", XmlSpawner.defProximityTriggerSound);
                                                    }
                                                    catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                                }
                                                else
                                                    if (e.Arguments[0].ToLower() == "proximityrange")
                                                    {
                                                        try
                                                        {
                                                            XmlSpawner.defProximityRange = Convert.ToInt32(e.Arguments[1]);
                                                            m.SendMessage("ProximityRange = {0}", XmlSpawner.defProximityRange);
                                                        }
                                                        catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                                    }
                                                    else
                                                        if (e.Arguments[0].ToLower() == "triggerprobability")
                                                        {
                                                            try
                                                            {
                                                                XmlSpawner.defTriggerProbability = Convert.ToDouble(e.Arguments[1]);
                                                                m.SendMessage("TriggerProbability = {0}", XmlSpawner.defTriggerProbability);
                                                            }
                                                            catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                                        }
                                                        else
                                                            if (e.Arguments[0].ToLower() == "todstart")
                                                            {
                                                                try
                                                                {
                                                                    XmlSpawner.defTODStart = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
                                                                    m.SendMessage("TODStart = {0}", XmlSpawner.defTODStart);
                                                                }
                                                                catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                                            }
                                                            else
                                                                if (e.Arguments[0].ToLower() == "todend")
                                                                {
                                                                    try
                                                                    {
                                                                        XmlSpawner.defTODEnd = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
                                                                        m.SendMessage("TODEnd = {0}", XmlSpawner.defTODEnd);
                                                                    }
                                                                    catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                                                }
                                                                else
                                                                    if (e.Arguments[0].ToLower() == "stackamount")
                                                                    {
                                                                        try
                                                                        {
                                                                            XmlSpawner.defAmount = Convert.ToInt32(e.Arguments[1]);
                                                                            m.SendMessage("StackAmount = {0}", XmlSpawner.defAmount);
                                                                        }
                                                                        catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                                                    }
                                                                    else
                                                                        if (e.Arguments[0].ToLower() == "duration")
                                                                        {
                                                                            try
                                                                            {
                                                                                XmlSpawner.defDuration = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
                                                                                m.SendMessage("Duration = {0}", XmlSpawner.defDuration);
                                                                            }
                                                                            catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                                                        }
                                                                        else
                                                                            if (e.Arguments[0].ToLower() == "group")
                                                                            {
                                                                                try
                                                                                {
                                                                                    XmlSpawner.defIsGroup = Convert.ToBoolean(e.Arguments[1]);
                                                                                    m.SendMessage("Group = {0}", XmlSpawner.defIsGroup);
                                                                                }
                                                                                catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                                                            }
                                                                            else
                                                                                if (e.Arguments[0].ToLower() == "team")
                                                                                {
                                                                                    try
                                                                                    {
                                                                                        XmlSpawner.defTeam = Convert.ToInt32(e.Arguments[1]);
                                                                                        m.SendMessage("Team = {0}", XmlSpawner.defTeam);
                                                                                    }
                                                                                    catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                                                                }
                                                                                else
                                                                                    if (e.Arguments[0].ToLower() == "todmode")
                                                                                    {
                                                                                        int todmode = (int)TODModeType.Realtime;
                                                                                        try
                                                                                        {
                                                                                            todmode = Convert.ToInt32(e.Arguments[1]);
                                                                                            switch (todmode)
                                                                                            {
                                                                                                case (int)TODModeType.Gametime:
                                                                                                    XmlSpawner.defTODMode = TODModeType.Gametime;
                                                                                                    break;
                                                                                                case (int)TODModeType.Realtime:
                                                                                                    XmlSpawner.defTODMode = TODModeType.Realtime;
                                                                                                    break;
                                                                                            }
                                                                                            m.SendMessage("TODMode = {0}", XmlSpawner.defTODMode);
                                                                                        }
                                                                                        catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                                                                    }
                                                                                    else
                                                                                        if (e.Arguments[0].ToLower() == "maxrefractory")
                                                                                        {
                                                                                            try
                                                                                            {
                                                                                                XmlSpawner.defMaxRefractory = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
                                                                                                m.SendMessage("MaxRefractory = {0}", XmlSpawner.defMaxRefractory);
                                                                                            }
                                                                                            catch { m.SendMessage("invalid value : {0}", e.Arguments[1]); }
                                                                                        }
                                                                                        else
                                                                                            if (e.Arguments[0].ToLower() == "minrefractory")
                                                                                            {
                                                                                                try
                                                                                                {
                                                                                                    XmlSpawner.defMinRefractory = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
                                                                                                    m.SendMessage("MinRefractory = {0}", XmlSpawner.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}", XmlSpawner.defTriggerProbability);
                m.SendMessage("ProximityRange = {0}", XmlSpawner.defProximityRange);
                m.SendMessage("ProximityTriggerSound = {0}", XmlSpawner.defProximityTriggerSound);
                m.SendMessage("MinRefractory = {0}", XmlSpawner.defMinRefractory);
                m.SendMessage("MaxRefractory = {0}", XmlSpawner.defMaxRefractory);
                m.SendMessage("TODStart = {0}", XmlSpawner.defTODStart);
                m.SendMessage("TODEnd = {0}", XmlSpawner.defTODEnd);
                m.SendMessage("TODMode = {0}", XmlSpawner.defTODMode);
                m.SendMessage("StackAmount = {0}", XmlSpawner.defAmount);
                m.SendMessage("Duration = {0}", XmlSpawner.defDuration);
                m.SendMessage("Group = {0}", XmlSpawner.defIsGroup);
                m.SendMessage("Team = {0}", XmlSpawner.defTeam);
                m.SendMessage("RelativeHome = {0}", XmlSpawner.defRelativeHome);
                m.SendMessage("SpawnRange = {0}", XmlSpawner.defSpawnRange);
                m.SendMessage("HomeRange = {0}", XmlSpawner.defHomeRange);
                m.SendMessage("MinDelay = {0}", XmlSpawner.defMinDelay);
                m.SendMessage("MaxDelay = {0}", XmlSpawner.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)
        {
            ArrayList ToShow = new ArrayList();
            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 (Item i in ToShow)
            {

                XmlSpawner xml_item = (XmlSpawner)i;
                // 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)
                {
                    Container root_item = xml_item.RootParent as Container;
                    // calculate a world location for the static.  Position it just above the container
                    int x = root_item.Location.X;
                    int y = root_item.Location.Y;
                    int z = root_item.Location.Z + 10;

                    Static s = new Static(ShowItemId);
                    s.Visible = false;
                    s.MoveToWorld(new Point3D(x, y, z), root_item.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)
        {
            ArrayList ToDelete = new ArrayList();
            foreach (Item item in World.Items.Values)
            {
                if (item is XmlSpawner)
                {
                    item.Visible = false;
                    item.Movable = false;
                    item.Hue = 0;
                    item.ItemID = BaseItemId;

                    // get rid of the external static marker for container-held spawners
                    // check anything that might have been tagged with a container static
                    XmlSpawner xml_item = (XmlSpawner)item;
                    if (xml_item.m_ShowContainerStatic != null && !xml_item.m_ShowContainerStatic.Deleted)
                    {
                        ToDelete.Add(item);
                    }
                }
            }
            foreach (Item i in ToDelete)
            {
                XmlSpawner xml_item = (XmlSpawner)i;
                if (xml_item.m_ShowContainerStatic != null && !xml_item.m_ShowContainerStatic.Deleted)
                {
                    xml_item.m_ShowContainerStatic.Delete();
                }
            }
        }

        [Usage("XmlGo <map> | <map> <x> <y> [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)
            {
                // Get the map
                Map NewMap = null;
                string MapName = e.Arguments[0];

                // 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 <map> | <map> <x> <y> [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 { }
            }
            // 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)
                {

                    XmlSpawner spawner = (XmlSpawner)item;

                    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;
            //if((currentcount + savings) > 0)
            //{
            //	percent = 100*savings/(currentcount + savings);
            //}

            int maxpercent = 0;
            if (totalcount > 0)
            {
                percent = 100 * savings / totalcount;
                maxpercent = 100 * maxcount / totalcount;
            }

            e.Mobile.SendMessage(
                "Running XmlSpawner version {9}\n" +
                "Smartspawning access level is {11}\n" +
                "--------------------------------\n" +
                "{0} XmlSpawners\n" +
                "{1} are configured for SmartSpawning\n" +
                "{2} are currently inactivated\n" +
                "{10} 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, Version, 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 { }
            }
            int count = 0;
            int maxcount = 0;
            foreach (Item item in World.Items.Values)
            {
                if (item is XmlSpawner)
                {

                    XmlSpawner spawner = (XmlSpawner)item;

                    // 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] as SpawnObject;

                        if (so == null) continue;

                        string typestr = so.TypeName;

                        Type type = null;

                        if (typestr != null)
                            type = SpawnerType.GetType(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 (System.IO.File.Exists(filename) == true)
            {
                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 (System.IO.Directory.Exists(filename) == true)
                {
                    // 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", ((SpawnerPrefix != null && SpawnerPrefix.Length > 0) ? " 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 != null && 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) == true))
                        {
                            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)
                                {
                                    XmlSpawner CheckXmlSpawner = (XmlSpawner)i;
                                    // 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 = (XmlSpawner)i;
                                        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 <SpawnFile or directory> [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].ToString());
                    int processedmaps;
                    int processedspawners;
                    XmlUnLoadFromFile(filename, SpawnerPrefix, e.Mobile, out processedmaps, out processedspawners);
                }
                else
                    e.Mobile.SendMessage("Usage:  {0} <SpawnFile or directory>", e.Command);
            }
            else
                e.Mobile.SendMessage("You do not have rights to perform this command.");
        }

        [Usage("XmlImportMap <mapfile or directory>")]
        [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].ToString();

                    int processedmaps;
                    int processedspawners;
                    XmlImportMap(filename, e.Mobile, out processedmaps, out processedspawners);
                }
                else
                    e.Mobile.SendMessage("Usage:  {0} <MapFile>", 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 (System.IO.File.Exists(filename) == true)
            {
                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 (System.IO.Directory.Exists(filename) == true)
                {
                    // 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;

                            int spawnid = 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]);
                                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 = SpawnerType.GetType(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);

                                if (hasvendor)
                                {
                                    // force vendor spawners to behave like the distro
                                    spawner.SpawnRange = 0;
                                }
                                else
                                {
                                    spawner.SpawnRange = spawnrange;
                                }

                                spawner.m_PlayerCreated = true;
                                string fromname = null;
                                if (from != null) fromname = from.Name;
                                spawner.LastModifiedBy = fromname;
                                spawner.FirstModifiedBy = fromname;
                                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);

                                    spawner.SpawnRange = spawnrange;
                                    spawner.m_PlayerCreated = true;

                                    spawner.LastModifiedBy = fromname;
                                    spawner.FirstModifiedBy = fromname;
                                    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;
                            int spawnid = 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]);
                                            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 != null && 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 = SpawnerType.GetType(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);

                                if (hasvendor)
                                {
                                    // force vendor spawners to behave like the distro
                                    spawner.SpawnRange = 0;
                                }
                                else
                                {
                                    spawner.SpawnRange = spawnrange;
                                }

                                spawner.m_PlayerCreated = true;
                                string fromname = null;
                                if (from != null) fromname = from.Name;
                                spawner.LastModifiedBy = fromname;
                                spawner.FirstModifiedBy = fromname;
                                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);

                                    spawner.SpawnRange = spawnrange;
                                    spawner.m_PlayerCreated = true;

                                    spawner.LastModifiedBy = fromname;
                                    spawner.FirstModifiedBy = fromname;
                                    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);
                            }
                        }
        }



        //------------------------------------------------------------------------------------------------------------------------------------
        // The following code was taken from Sno's xml exporter package and slightly modified to create xmlspawners instead of regular spawners
        //------------------------------------------------------------------------------------------------------------------------------------
        [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 != null && 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 <filename>");
        }

        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"));
            ArrayList 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((string)creaturesName[i], count);
                // check the type to see if there are vendors on it
                Type type = SpawnerType.GetType((string)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);

            if (hasvendor)
            {
                spawner.SpawnRange = 0;
            }
            else
            {
                spawner.SpawnRange = homeRange;
            }
            spawner.m_PlayerCreated = true;
            string fromname = null;
            if (from != null) fromname = from.Name;
            spawner.LastModifiedBy = fromname;
            spawner.FirstModifiedBy = fromname;

            spawner.MoveToWorld(location, map);
            if (!IsValidMapLocation(location, spawner.Map))
            {
                spawner.Delete();
                throw new Exception("Invalid spawner location.");
            }
        }

        private static ArrayList LoadCreaturesName(XmlElement node)
        {
            ArrayList names = new ArrayList();

            if (node != null)
            {
                foreach (XmlElement ele in node.GetElementsByTagName("creaturename"))
                {
                    if (ele != null)
                        names.Add(ele.InnerText);
                }
            }
            return names;
        }
        //------------------------------------------------------------------------------------------------------------------------------------
        // end of modified xml importer by Sno
        //------------------------------------------------------------------------------------------------------------------------------------


        [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 <filename>");
        }

        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.Now, 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.Now, 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);

            spawner.SpawnRange = spawnRange;
            spawner.m_PlayerCreated = true;
            string fromname = null;
            if (from != null) fromname = from.Name;
            spawner.LastModifiedBy = fromname;
            spawner.FirstModifiedBy = fromname;

            // 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 (System.IO.File.Exists(filename) == true)
            {
                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
                // check to see if it is a directory
                if (System.IO.Directory.Exists(filename) == true)
                {
                    // 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)
        {
            processedmaps = 0;
            processedspawners = 0;

            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)
        {
            processedmaps = 0;
            processedspawners = 0;

            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, true);
        }

        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(string.Format("Loading {0} objects{1} from file {2}.", "XmlSpawner",
                    ((SpawnerPrefix != null && SpawnerPrefix.Length > 0) ? " beginning with " + SpawnerPrefix : string.Empty), 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 != null && 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 ((SpawnerPrefix == null) || (SpawnerPrefix.Length == 0) || (SpawnName.StartsWith(SpawnerPrefix) == true))
                        {
                            // 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 ((int)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 { }

                            string SpawnObjectPropertyName = null;
                            try { SpawnObjectPropertyName = (string)dr["ObjectPropertyName"]; }
                            catch { }

                            // read in the object proximity target, this will be an object name, so have to do a search
                            // to find the item in the world.  Also have to test for redundancy
                            string triggerObjectName = null;
                            try { triggerObjectName = (string)dr["ObjectPropertyItemName"]; }
                            catch { }

                            // read in the target for the set command, this will be an object name, so have to do a search
                            // to find the item in the world.  Also have to test for redundancy
                            string setObjectName = null;
                            try { setObjectName = (string)dr["SetPropertyItemName"]; }
                            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)
                                    {
                                        XmlSpawner CheckXmlSpawner = (XmlSpawner)i;

                                        // 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 = (XmlSpawner)i;
                                            found_spawner = true;
                                        }
                                    }

                                    //look for containers with the spawn coordinates if the incontainer flag is set
                                    if (InContainer && !found_container && (i is Container) && (SpawnCentreX == i.Location.X) && (SpawnCentreY == i.Location.Y) &&
                                        (SpawnCentreZ == i.Location.Z || SpawnCentreZ == short.MinValue))
                                    {
                                        // assume this is the container that the spawner was in
                                        found_container = true;
                                        spawn_container = i as 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.Now, 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.Now);
                                            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);
                                //TheSpawn.Group = SpawnIsGroup;\

                                string fromname = null;
                                if (from != null) fromname = from.Name;
                                TheSpawn.LastModifiedBy = fromname;
                                TheSpawn.FirstModifiedBy = fromname;

                                // 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)
                            {
                                XmlSpawner CheckXmlSpawner = (XmlSpawner)i;

                                // 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 = (XmlSpawner)i;
                                    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 (setObjectName != null && setObjectName.Length > 0)
                            {
                                // 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.Now);
                                            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 (triggerObjectName != null && triggerObjectName.Length > 0)
                            {
                                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.Now);
                                            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 (System.IO.Directory.Exists(XmlSpawnDir) == true)
            {
                // get it from the defaults directory if it exists
                dirname = String.Format("{0}/{1}", XmlSpawnDir, filename);
                found = System.IO.File.Exists(dirname) || System.IO.Directory.Exists(dirname);
            }

            if (!found)
            {
                // otherwise just get it from the main installation dir
                dirname = filename;
            }

            return dirname;
        }

        [Usage("XmlNewLoad <SpawnFile or directory> [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].ToString());

                    // 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} <SpawnFile or directory> [SpawnerPrefixFilter]", e.Command);
            }
            else
                e.Mobile.SendMessage("You do not have rights to perform this command.");
        }

        [Usage("XmlLoad <SpawnFile or directory> [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)
        {
            if (e.Mobile.AccessLevel >= DiskAccessLevel)
            {
                if (e.Arguments.Length >= 1)
                {
                    string filename = LocateFile(e.Arguments[0].ToString());

                    // 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, false, out processedmaps, out processedspawners);
                }
                else
                    e.Mobile.SendMessage("Usage:  {0} <SpawnFile or directory> [SpawnerPrefixFilter]", e.Command);
            }
            else
                e.Mobile.SendMessage("You do not have rights to perform this command.");
        }

        [Usage("XmlNewLoadHere <SpawnFile or directory> [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].ToString());

                    // 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} <SpawnFile or directory> [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} <SpawnFile or directory> [SpawnerPrefixFilter][-maxrange range]", e.Command);
            }
            else
                e.Mobile.SendMessage("You do not have rights to perform this command.");
        }

        [Usage("XmlLoadHere <SpawnFile or directory> [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].ToString());

                    // 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} <SpawnFile or directory> [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} <SpawnFile or directory> [SpawnerPrefixFilter][-maxrange range]", e.Command);
            }
            else
                e.Mobile.SendMessage("You do not have rights to perform this command.");
        }

        [Usage("XmlSaveOld <SpawnFile> [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 <SpawnFile> [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 <SpawnFile> [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);
        }

        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} <SpawnFile> [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].ToString();

            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 = String.Format("{0}/{1}", XmlSpawnDir, filename);
            }
            else
            {
                // otherwise just put it in the main installation dir
                dirname = filename;
            }

            if (SaveAllMaps == true)
                e.Mobile.SendMessage(string.Format("Saving {0} objects{1} to file {2} from {3}.", "XmlSpawner",
                    ((SpawnerPrefix != null && SpawnerPrefix.Length > 0) ? " 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",
                    ((SpawnerPrefix != null && SpawnerPrefix.Length > 0) ? " beginning with " + SpawnerPrefix : string.Empty), dirname));


            ArrayList saveslist = new ArrayList();

            // Add each spawn point to the list
            foreach (Item i in World.Items.Values)
            {
                if (i is XmlSpawner && !i.Deleted && ((SaveAllMaps == true) || (i.Map == e.Mobile.Map))
                    //check for mob carried spawners and ignore them
                    && !(i.RootParent is Mobile)
                    && (SpawnerPrefix == null || (SpawnerPrefix.Length == 0) || (i.Name != null && i.Name.StartsWith(SpawnerPrefix))))
                {
                    saveslist.Add(i);
                }
            }

            // save the list
            SaveSpawnList(e.Mobile, saveslist, dirname, oldformat, true);
        }

        public static bool SaveSpawnList(ArrayList savelist, Stream stream)
        {
            return SaveSpawnList(null, savelist, null, stream, false, false);
        }

        public static bool SaveSpawnList(Mobile from, ArrayList savelist, string dirname, bool oldformat, bool verbose)
        {
            if (dirname == null || dirname.Length == 0) return false;


            bool save_ok = true;
            System.IO.FileStream fs = null;

            try
            {
                // Create the FileStream to write with.
                fs = new System.IO.FileStream(dirname, System.IO.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, ArrayList 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("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");
            if (oldformat)
            {
                ds.Tables[SpawnTablePointName].Columns.Add("Objects");
            }
            else
            {
                ds.Tables[SpawnTablePointName].Columns.Add("Objects2");
            }

            // 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"] = (string)sp.Name;

                // Set the unqiue id
                dr["UniqueId"] = (string)sp.m_UniqueId;

                // Get the map name
                dr["Map"] = (string)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"] = (int)sp.m_X;
                dr["Y"] = (int)sp.m_Y;
                dr["Width"] = (int)sp.m_Width;
                dr["Height"] = (int)sp.m_Height;

                // check to see if this is in a container
                if (sp.RootParent is Container)
                {
                    dr["CentreX"] = (int)(((Container)(sp.RootParent)).Location.X);
                    dr["CentreY"] = (int)(((Container)(sp.RootParent)).Location.Y);
                    dr["CentreZ"] = (int)(((Container)(sp.RootParent)).Location.Z);
                    dr["ContainerX"] = (int)(sp.Location.X);
                    dr["ContainerY"] = (int)(sp.Location.Y);
                    dr["ContainerZ"] = (int)(sp.Location.Z);
                    dr["InContainer"] = true;
                }
                else
                {
                    dr["CentreX"] = (int)sp.Location.X;
                    dr["CentreY"] = (int)sp.Location.Y;
                    dr["CentreZ"] = (int)sp.Location.Z;
                    //dr["ContainerX"] = 0;
                    //dr["ContainerY"] = 0;
                    //dr["ContainerZ"] = 0;
                    dr["InContainer"] = false;
                }
                dr["Range"] = (int)sp.m_HomeRange;
                dr["MaxCount"] = (int)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"] = (double)sp.m_TODStart.TotalMinutes;
                dr["TODEnd"] = (double)sp.m_TODEnd.TotalMinutes;
                dr["TODMode"] = (int)sp.m_TODMode;
                dr["KillReset"] = (int)sp.m_KillReset;
                dr["MinRefractory"] = (double)sp.m_MinRefractory.TotalMinutes;
                dr["MaxRefractory"] = (double)sp.m_MaxRefractory.TotalMinutes;
                dr["Duration"] = (double)sp.m_Duration.TotalMinutes;
                dr["DespawnTime"] = (double)sp.m_DespawnTime.TotalHours;
                dr["ExternalTriggering"] = (bool)sp.m_ExternalTriggering;

                dr["ProximityRange"] = (int)sp.m_ProximityRange;
                dr["ProximityTriggerSound"] = (int)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["SpeechTrigger"] = sp.m_SpeechTrigger;
                dr["SkillTrigger"] = sp.m_SkillTrigger;
                dr["Amount"] = (int)sp.m_StackAmount;
                dr["Team"] = (int)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) && (sp.m_WayPoint.Name != null) && (sp.m_WayPoint.Name.Length > 0))
                    {
                        waystr = sp.m_WayPoint.Name;
                    }
                    else
                    {
                        waystr = String.Format("SERIAL,{0}", sp.m_WayPoint.Serial);
                    }
                }
                dr["WayPoint"] = waystr;

                dr["IsGroup"] = (bool)sp.m_Group;
                dr["IsRunning"] = (bool)sp.m_Running;
                dr["IsHomeRangeRelative"] = (bool)sp.m_HomeRangeIsRelative;
                if (oldformat)
                {
                    dr["Objects"] = (string)sp.GetSerializedObjectList();
                }
                else
                {
                    dr["Objects2"] = (string)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 == true)
                    e.Mobile.SendMessage("Removing ALL XmlSpawner objects from the world{0}.", ((SpawnerPrefix != null && SpawnerPrefix.Length > 0) ? " beginning with " + SpawnerPrefix : string.Empty));
                else
                    e.Mobile.SendMessage("Removing ALL XmlSpawner objects from {0}{1}.", e.Mobile.Map, ((SpawnerPrefix != null && SpawnerPrefix.Length > 0) ? " beginning with " + SpawnerPrefix : string.Empty));

                // Delete Xml spawner's in the world based on the mobiles current map
                int Count = 0;
                ArrayList ToDelete = new ArrayList();
                foreach (Item i in World.Items.Values)
                {
                    if ((i is XmlSpawner) && (WipeAll == true || 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 == true)
                    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 == true)
                    e.Mobile.SendMessage("Respawning ALL XmlSpawner objects from the world{0}.", ((SpawnerPrefix != null && SpawnerPrefix.Length > 0) ? " beginning with " + SpawnerPrefix : string.Empty));
                else
                    e.Mobile.SendMessage("Respawning ALL XmlSpawner objects from {0}{1}.", e.Mobile.Map, ((SpawnerPrefix != null && SpawnerPrefix.Length > 0) ? " beginning with " + SpawnerPrefix : string.Empty));

                // Respawn Xml spawner's in the world based on the mobiles current map
                int Count = 0;
                ArrayList ToRespawn = new ArrayList();
                foreach (Item i in World.Items.Values)
                {
                    try
                    {

                        if ((i is XmlSpawner) && (RespawnAll == true || 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.Respawn();
                }

                if (RespawnAll == true)
                    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{}

			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.Now - XmlSpawner._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( XmlSpawner._traceCount[i] > 0)
				{
					double load = 0;
					if(processtime > 0)
					{
						load  = ((double)XmlSpawner._traceTotal[i].TotalMilliseconds)/processtime;
					}
						 Console.WriteLine( "{0} ({4}) {1,21} / {2} calls = {3:####.####} ms/call, {5:p3}",
						i,XmlSpawner._traceTotal[i], XmlSpawner._traceCount[i],((double)XmlSpawner._traceTotal[i].TotalMilliseconds)/XmlSpawner._traceCount[i],
						XmlSpawner._traceName[i], load);
				}
			}
		}

		public static void XmlResetTrace_OnCommand( CommandEventArgs e )
		{

			if( e.Arguments.Length >= 0 )
			{
				 for(int i=0;i<MaxTraces;i++)
				 {
						XmlSpawner._traceCount[i] = 0;
						XmlSpawner._traceTotal[i] = TimeSpan.Zero;
				}
				XmlSpawner._traceStartTime = DateTime.Now;

				Process currentprocess = Process.GetCurrentProcess();
				_startProcessTime = currentprocess.UserProcessorTime.TotalMilliseconds;

				 Console.WriteLine( "Traces reset");
			}
		}
#endif

        #endregion

        #region Constructors

        [Constructable]
        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);
        }

        [Constructable]
        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);
        }

        [Constructable]
        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);
        }

        [Constructable]
        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;

            if ((name != null) && (name.Length > 0))
                Name = name;
            else
                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 ArrayList();

            // Assign the list of objects to spawn
            SpawnObjects = objectsToSpawn;

            // Kick off the process
            DoTimer(TimeSpan.FromSeconds(1));
        }

        public XmlSpawner(Serial serial)
            : base(serial)
        {
        }

        #endregion

        #region Defrag methods

        public void Defrag(bool killtest)
        {

            if (m_SpawnObjects == null) return;

            bool removed = false;
            int total_removed = 0;

            ArrayList deletelist = new ArrayList();
            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 item = (Item)o;
                        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.Now - DespawnTime) && (item.Parent == this.Parent)
                            && (!ItemFlags.GetTaken(item) || (item.Parent != null && (item.Parent == this.Parent)))) // can despawn if just moved within the same container
                        {
                            //item.Delete();
                            deletelist.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 != this.Parent) // different container
                            || (ItemFlags.GetTaken(item) && (item.Parent == null || (item.Parent != this.Parent))))   // taken and in the world, or a different container
                        {
                            so.SpawnedObjects.Remove(o);
                            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 m = (Mobile)o;

                        // check to see if the spawn has been idle for a long time
                        // if it has then reposition it because it might be in an inaccessible location
                        // note, vendors and special cases of positioning from the Spawn method will not get relocated
                        // i'm not saying I like those special cases, but they should be treated consistently
                        // doesnt work properly under RunUO 2.0 and also doesnt properly take spawn control keywords into consideration
                        // when repositioning, so disable this for now
                        /*
                        if (SpawnIdleTime > 0 && !m.Deleted && !(m is BaseVendor) && (m.CreationTime < DateTime.Now - TimeSpan.FromHours(SpawnIdleTime))
                            && m.Map != null && m.Map != Map.Internal && !m.Map.GetSector(m.Location).Active)
                        {
                            // determine whether the requiresurface flag is set
                            m.Location = GetSpawnPosition(so.RequireSurface, m);

                            // and reset the creation time (simulates respawning the identical mob at a new location)
                            //m.CreationTime = DateTime.Now;
                        }
                         * */
                        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 && !m.Deleted && (m.CreationTime < DateTime.Now - DespawnTime)
                            && m.Map != null && m.Map != Map.Internal && !m.Map.GetSector(m.Location).Active)
                        {
                            //m.Delete();
                            deletelist.Add(m);
                            despawned = true;
                        }

                        if (m.Deleted || despawned)
                        {
                            // Remove the delete mobile from the list
                            so.SpawnedObjects.Remove(o);
                            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 (m is BaseCreature)
                        {
                            BaseCreature b = (BaseCreature)m;
                            // Check if the creature has been tamed or previously tamed and released
                            // and if it is, remove it from the list of spawns
                            if (b.Controlled || b.IsStabled || (b.Owners != null && b.Owners.Count > 0))
                            {
                                so.SpawnedObjects.Remove(o);
                                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)
                        {
                            BaseXmlSpawner.KeywordTag tag = (BaseXmlSpawner.KeywordTag)o;
                            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(deletelist);

            // Check if anything has been removed
            if (removed == true)
                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;

            ArrayList ToDelete = new ArrayList();
            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)
                    {
                        BaseXmlSpawner.KeywordTag sot = (BaseXmlSpawner.KeywordTag)o;
                        // clear the tags except for gump and delay tags
                        if (sot != null && sot.Type == 2)
                        {
                            ToDelete.Add(sot);
                            so.SpawnedObjects.Remove(o);
                            x--;
                        }

                    }
                }
            }

            foreach (BaseXmlSpawner.KeywordTag i in ToDelete)
            {
                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;
            ArrayList ToDelete = new ArrayList();
            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)
                    {
                        BaseXmlSpawner.KeywordTag sot = (BaseXmlSpawner.KeywordTag)o;
                        // clear the tags except for gump and delay tags
                        if (sot != null && (all || ((sot.Flags & BaseXmlSpawner.KeywordFlags.Defrag) != 0)))
                        {
                            ToDelete.Add(sot);
                            so.SpawnedObjects.Remove(o);
                            x--;
                            removed = true;
                        }

                    }
                }
            }

            foreach (BaseXmlSpawner.KeywordTag i in ToDelete)
            {
                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 == true)
                InvalidateProperties();
        }

        public void DeleteGumpTags()
        {
            if (m_SpawnObjects == null) return;
            bool removed = false;
            ArrayList ToDelete = new ArrayList();
            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)
                    {
                        BaseXmlSpawner.KeywordTag sot = (BaseXmlSpawner.KeywordTag)o;
                        // clear the gump tags
                        if (sot != null && sot.Type == 1)
                        {
                            ToDelete.Add(sot);
                            so.SpawnedObjects.Remove(o);
                            x--;
                            removed = true;
                        }

                    }
                }
            }

            foreach (BaseXmlSpawner.KeywordTag i in ToDelete)
            {
                if (i != null && !i.Deleted)
                {
                    i.Delete();
                }
            }

            // Check if anything has been removed
            if (removed == true)
                InvalidateProperties();
        }

        public void DeleteTag(BaseXmlSpawner.KeywordTag tag)
        {
            if (m_SpawnObjects == null) return;
            bool removed = false;
            ArrayList ToDelete = new ArrayList();
            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)
                    {
                        BaseXmlSpawner.KeywordTag sot = (BaseXmlSpawner.KeywordTag)o;
                        // clear the matching tags
                        if (sot != null && sot == tag)
                        {
                            ToDelete.Add(sot);
                            so.SpawnedObjects.Remove(o);
                            x--;
                            removed = true;
                        }

                    }
                }
            }

            foreach (BaseXmlSpawner.KeywordTag i in ToDelete)
            {
                if (i != null && !i.Deleted)
                {
                    i.Delete();
                }
            }

            // Check if anything has been removed
            if (removed == true)
                InvalidateProperties();
        }

        #endregion

        #region SequentialSpawning methods

        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 = (SpawnObject)m_SpawnObjects[i];

                if (s.SubGroup == sgroup)
                    nsub++;
            }

            return nsub;
        }

        private int RandomAvailableSpawnIndex()
        {
            // get spawn indices randomly from all available spawns independent of group
            return (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<int> 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 = (SpawnObject)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<int>();
                    }
                    sgrouplist.Add(s.SubGroup);
                }
            }

            for (int i = 0; i < m_SpawnObjects.Count; i++)
            {
                SpawnObject s = (SpawnObject)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 = (SpawnObject)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);
            }
            else
            {
                // 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 = (SpawnObject)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 = (SpawnObject)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);
            }
            else
            {
                // 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 = (SpawnObject)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));
            else
                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 = (SpawnObject)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 (((SpawnObject)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 = (SpawnObject)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 = (SpawnObject)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 = (SpawnObject)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 = (SpawnObject)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);
        }
        #endregion

        #region Spawn methods

        int killcount_held = 0;

        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 (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((Mobile)null, null, false);
                }

                // check for proximity triggers without movement activation
                if (m_ProximityRange >= 0 && CanSpawn)
                {
                    // check all nearby players
                    foreach (Mobile p in GetMobilesInRange(m_ProximityRange))
                    {
                        if (ValidPlayerTrig(p))
                            CheckTriggers(p, null, true);
                    }
                }

                if (m_Group == true)
                {
                    // 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 = Respawn();

                        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 = Respawn();

                            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);

                    if (triedtospawn) ClearGOTOTags();
                    // this will maintain any sequential holds if spawning was suppressed due to triggering
                    // if nothing was spawned or triggered, then restore the hold status to previous state
                    //if(!triedtospawn) HoldSequence = hadhold;

                    if (!FreeRun)
                    {
                        m_mob_who_triggered = null;
                        m_skill_that_triggered = XmlSpawnerSkillCheck.RegisteredSkill.Invalid;
                    }

                }

                // 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 = (SpawnObject)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)
        {
            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 = 0;

                // see if sequential spawning has been selected
                if (m_SequentialSpawning >= 0)
                {
                    // if so then use its value to get the index of the first available spawn entry in the next subgroup to be spawned
                    // note, if the current sequence finds a zero group then the spawn will be picked at random from it
                    SpawnIndex = GetCurrentAvailableSequentialSpawnIndex(m_SequentialSpawning);
                }
                else
                {
                    // if sequential spawning is not set then select the next spawn at random
                    SpawnIndex = RandomAvailableSpawnIndex();
                }

                // no spawns are available so no point in continuing
                if (SpawnIndex < 0)
                {
                    ResetProximityActivated();
                    return (true);
                }

                SpawnObject sobj = (SpawnObject)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);

                    /*
                    for( int j = 0; j < m_SpawnObjects.Count; j++)
                    {
                        SpawnObject so = (SpawnObject)m_SpawnObjects[j];

                        if(so.SubGroup == sgroup)
                        {
                            // get the SpawnsPerTick count and spawn up to that number
                            bool success = Spawn( j, smartspawn, so.SpawnsPerTick );
									
                            if(success && !smartspawn)
                                RefreshNextSpawnTime(so);
                        }
                    }
                     */
                }
                else
                {
                    // Found a valid spawn object so spawn it and see if it successful
                    if (Spawn(SpawnIndex, smartspawn, sobj.SpawnsPerTick) == true)
                    {
                        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)
        {
            if (m_SpawnObjects == null || index >= m_SpawnObjects.Count) return false;

            bool didspawn = false;

            SpawnObject so = (SpawnObject)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))
                {
                    // 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)
        {
            return Spawn(index, smartspawn, count, false);
        }

        // spawn an individual entry by index up to count times
        public bool Spawn(int index, bool smartspawn, int count, bool ignoreloopprotection)
        {
            return Spawn(index, smartspawn, count, -1, Point3D.Zero, ignoreloopprotection);
        }

        // spawn an individual entry by spawn object
        public void Spawn(string SpawnObjectTypeName, bool smartspawn, int packrange, Point3D packcoord)
        {
            if (m_SpawnObjects == null) return;
            for (int i = 0; i < m_SpawnObjects.Count; i++)
            {
                if (((SpawnObject)m_SpawnObjects[i]).TypeName.ToUpper() == SpawnObjectTypeName.ToUpper())
                {

                    if (Spawn(i, smartspawn, packrange, packcoord))
                    {
                        RefreshNextSpawnTime((SpawnObject)m_SpawnObjects[i]);
                    }
                    break;
                }
                else
                {

                }
            }
        }

        // spawn an individual entry by index
        public void Spawn(string SpawnObjectTypeName, bool smartspawn)
        {
            Spawn(SpawnObjectTypeName, smartspawn, -1, Point3D.Zero);
        }

        // spawn an individual entry by index
        public bool Spawn(int index, bool smartspawn, int packrange, Point3D packcoord)
        {
            return Spawn(index, smartspawn, packrange, packcoord, false);
        }

        // spawn an individual entry by index
        public bool Spawn(int index, bool smartspawn, int packrange, Point3D packcoord, bool ignoreloopprotection)
        {

            Map map = this.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] as SpawnObject;

            // 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.Now)
                    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, m_mob_who_triggered, TheSpawn.TypeName);

                // random positioning is the default
                List<SpawnPositionInfo> 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<SpawnPositionInfo>();
                        }
                        // parse any comma args
                        string[] keyvalueargs = BaseXmlSpawner.ParseCommaArgs(args[0], 10);

                        if (keyvalueargs.Length > 0)
                        {

                            switch (keyvalueargs[0])
                            {
                                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], m_mob_who_triggered, out this.status_str)) return false;

                                    }
                                    else
                                    {
                                        this.status_str = "invalid #CONDITION specification: " + args[0];
                                    }
                                    break;
                                default:
                                    this.status_str = "invalid # specification: " + args[0];
                                    break;
                            }
                        }
                    }

                    // get the rest of the spawn entry
                    if (args.Length > 1)
                    {
                        substitutedtypeName = args[1].Trim();
                    }
                    else
                    {
                        substitutedtypeName = 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, requiresurface, spawnpositioning,
                        m_mob_who_triggered, this.Location, this.Map, new XmlGumpCallback(SpawnerGumpCallback), out status_str);

                    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;
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {

                    // its a regular type descriptor so find out what it is
                    Type type = null;
                    if (typeName != null)
                    {
                        type = SpawnerType.GetType(typeName);
                    }

                    // dont try to spawn invalid types, or Mobile type spawns in containers
                    if (type != null && !(this.Parent != null && (type == typeof(Mobile) || type.IsSubclassOf(typeof(Mobile)))))
                    {

                        string[] arglist = BaseXmlSpawner.ParseString(substitutedtypeName, 3, "/");

                        object o = CreateObject(type, arglist[0]);

                        if (o == null)
                        {
                            this.status_str = "invalid type specification: " + arglist[0];
                            return true;
                        }
                        try
                        {
                            if (o is Mobile)
                            {
                                // if this is in any container such as a pack the xyz values are invalid as map coords so dont spawn the mob
                                if (this.Parent is Container)
                                {
                                    ((Mobile)o).Delete();

                                    return true;
                                }

                                Mobile m = (Mobile)o;

                                // add the mobile to the spawned list
                                TheSpawn.SpawnedObjects.Add(m);

                                m.Spawner = this;

                                Point3D loc;

                                /*
                                if( ( m is BaseVendor ) &&
                                    ( this.Map.CanFit( this.Location, SpawnFitSize, true, false ) == true ) )
                                {
                                    loc = this.Location;
                                } 
                                else
                                {
                                    loc = GetSpawnPosition(requiresurface);
                                }
                                */
                                loc = GetSpawnPosition(requiresurface, packrange, packcoord, spawnpositioning, m);

                                if (!smartspawn)
                                {
                                    m.OnBeforeSpawn(loc, map);
                                }

                                m.MoveToWorld(loc, map);

                                if (m is BaseCreature)
                                {
                                    BaseCreature c = (BaseCreature)m;
                                    c.RangeHome = m_HomeRange;
                                    c.CurrentWayPoint = m_WayPoint;

                                    if (m_Team > 0)
                                        c.Team = m_Team;

                                    // Check if this spawner uses absolute (from spawnER location)
                                    // or relative (from spawnED location) as the mobiles home point
                                    if (m_HomeRangeIsRelative == true)
                                        c.Home = m.Location; // Mobiles spawned location is the home point
                                    else
                                        c.Home = this.Location; // Spawners location is the home point
                                }

                                // if the object has an OnSpawned method, then invoke it
                                if (!smartspawn)
                                {
                                    m.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, m, m_mob_who_triggered, this, out status_str);

                                // if the object has an OnAfterSpawnAndModify method, then invoke it
                                //BaseXmlSpawner.InvokeOnAfterSpawnAndModify(o);

                                if (status_str != null)
                                {
                                    this.status_str = status_str;
                                }

                                InvalidateProperties();

                                // added the duration timer that begins on spawning
                                DoTimer2(m_Duration);

                                return true;
                            }
                            else if (o is Item)
                            {
                                Item item = (Item)o;

                                string status_str;

                                BaseXmlSpawner.AddSpawnItem(this, TheSpawn, item, this.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
                    {
                        this.status_str = "invalid type specification: " + typeName;
                        return true;
                    }
                }
            }
            return false;
        }

        public bool SpawnSubGroup(int sgroup)
        {
            return SpawnSubGroup(sgroup, false);
        }

        public bool SpawnSubGroup(int sgroup, bool smartspawn)
        {
            return SpawnSubGroup(sgroup, false, false);
        }

        public bool SpawnSubGroup(int sgroup, bool smartspawn, bool ignoreloopprotection)
        {
            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 = (SpawnObject)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);

                        if (success) didspawn = true;

                        if (success && !smartspawn)
                            RefreshNextSpawnTime(so);

                    }
                }

                // success if any of the subgroup spawned
                if (didspawn)
                {
                    return (true);
                }
            }
            return (false);
        }

        #endregion

        #region Spawn support methods

        public Point3D GetPackCoord(int sgroup)
        {
            Point3D packcoord = Point3D.Zero;

            for (int j = 0; j < m_SpawnObjects.Count; j++)
            {
                SpawnObject so = (SpawnObject)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)
                        {
                            return ((Item)o).Location;
                        }
                        else
                            if (o is Mobile)
                            {
                                return ((Mobile)o).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_skill_that_triggered = XmlSpawnerSkillCheck.RegisteredSkill.Invalid;
            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 m = (Mobile)o;

                        m.Map = Map;
                        m.Location = new Point3D(Location);
                    }
                    else if (o is Item)
                    {
                        Item item = (Item)o;

                        item.MoveToWorld(Location, Map);
                    }
                }
            }
        }

        public bool CheckRegionAssignment
        {
            get { return false; }
            set
            {
                if (value == true)
                {
                    // 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 == true)
            {
                // 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;
                m_skill_that_triggered = XmlSpawnerSkillCheck.RegisteredSkill.Invalid;
            }
        }

        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 bool Respawn()
        {
            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++)
            {
                if (Spawn(false)) triedtospawn = true;

                if (x < m_Count - 1 || OnHold) m_proximityActivated = keepProximityActivated;
            }
            if (!FreeRun)
            {
                m_mob_who_triggered = null;
                m_skill_that_triggered = XmlSpawnerSkillCheck.RegisteredSkill.Invalid;
            }

            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);

                if (x < m_Count - 1 || OnHold) m_proximityActivated = keepProximityActivated;
            }

            if (!FreeRun)
            {
                m_mob_who_triggered = null;
                m_skill_that_triggered = XmlSpawnerSkillCheck.RegisteredSkill.Invalid;
            }

            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(object x, object y)
            {
                SpawnObject a = x as SpawnObject;
                SpawnObject b = y as SpawnObject;
                if (a.SubGroup == b.SubGroup)
                {
                    // use the entry order as the secondary sort factor
                    return a.EntryOrder - b.EntryOrder;
                }
                else
                    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 (((SpawnObject)spawner.m_SpawnObjects[i]).SubGroup == sgroup)
                {
                    return (SpawnObject)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 (((SpawnObject)spawner.m_SpawnObjects[i]).SubGroup == sgroup)
                {
                    // find the first spawned object in the entry
                    if (((SpawnObject)spawner.m_SpawnObjects[i]).SpawnedObjects.Count > 0)
                    {
                        return ((SpawnObject)spawner.m_SpawnObjects[i]).SpawnedObjects[0];
                    }
                }
            }
            return null;
        }

        public static ArrayList GetSpawnedList(XmlSpawner spawner, int sgroup)
        {
            ArrayList newlist = new ArrayList();

            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 (((SpawnObject)spawner.m_SpawnObjects[i]).SubGroup == sgroup)
                {
                    // find the first spawned object in the entry

                    if (((SpawnObject)spawner.m_SpawnObjects[i]).SpawnedObjects.Count > 0)
                    {
                        for (int j = 0; j < ((SpawnObject)spawner.m_SpawnObjects[i]).SpawnedObjects.Count; j++)
                            newlist.Add(((SpawnObject)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 (((SpawnObject)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;
            }
        }

        private void RefreshNextSpawnTimes()
        {

            if (m_SpawnObjects != null && m_SpawnObjects.Count > 0)
            {
                for (int i = 0; i < m_SpawnObjects.Count; i++)
                {
                    SpawnObject so = m_SpawnObjects[i] as SpawnObject;

                    RefreshNextSpawnTime(so);
                }
            }
        }

        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] as SpawnObject;

                    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] as SpawnObject;

                    so.NextSpawn = DateTime.Now;
                }
            }
        }

        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.Now;
            }
            else
            {

                TimeSpan delay = TimeSpan.FromSeconds(Utility.RandomMinMax(mind, maxd));

                so.NextSpawn = DateTime.Now + 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 (waypointstr != null && waypointstr.Length > 0)
            {
                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)
                        {
                            int sernum = -1;
                            try { sernum = (int)Convert.ToUInt64(wayargs[1].Substring(2), 16); }
                            catch { }

                            if (sernum > -1)
                            {
                                IEntity e = World.FindEntity(sernum);

                                if (e is WayPoint)
                                    waypoint = e as WayPoint;

                            }
                        }
                        else
                        {
                            // improper serial format
                        }
                    }
                    else
                    {
                        // just look it up by name
                        Item wayitem = BaseXmlSpawner.FindItemByName(null, wayargs[0], "WayPoint");
                        if (wayitem is WayPoint)
                        {
                            waypoint = wayitem as WayPoint;
                        }
                    }
                }
            }

            return waypoint;
        }

        private static bool HasTileSurface(Map map, int X, int Y, int Z)
        {
            if (map == null) return false;
#if(RUNUO2RC1)
			ArrayList tiles = map.GetTilesAt(new Point2D(X, Y), true, true, true);
#else
            StaticTile[] tiles = map.Tiles.GetStaticTiles(X, Y, true);
            //List<Server.Tile> tiles = map.GetTilesAt(new Point2D(X, Y), true, true, true);
#endif
            if (tiles == null) return false;

            // go through the tiles and see if any are at the Z location
            foreach (object o in tiles)
            {

                if (o is StaticTile)
                {
                    StaticTile i = (StaticTile)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 Hashtable();
            }
            PropertyInfo prop = null;

            if (!holdSmartSpawningHash.Contains(o.GetType()))
            {

                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.Add(o.GetType(), prop);
            }
            else
            {
                // look it up in the hash table
                prop = (PropertyInfo)holdSmartSpawningHash[o.GetType()];
            }

            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 = this.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);
            int lowZ = 0, avgZ = 0, topZ = 0;

            bool surface, impassable;
            bool wet = false;

            map.GetAverageZ(x, y, ref lowZ, ref avgZ, ref 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);
            }

            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;
            else 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;
                else 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<Item> items = sector.Items;
            List<Mobile> 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;
                    else 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);
            }
            if (!Region.Find(new Point3D(x, y, z), this.Map).AllowSpawn())
                return false;

            return CanFit(x, y, z, 16, false, true, true, mob);
        }

        public bool HasRegionPoints(Region r)
        {
            if (r != null && r.Area.Length > 0) return true;
            else
                return false;
        }

        private void FindTileLocations(ref List<Point3D> locations, Map map, int startx, int starty, int width, int height, List<int> includetilelist, List<int> excludetilelist, TileFlag tileflag)
        {
            if (width < 0 || height < 0 || map == null) return;

            if (locations == null) locations = new List<Point3D>();

            bool includetile;
            bool excludetile;
            for (int x = startx; x <= startx + width; x++)
            {
                for (int y = starty; y <= starty + height; y++)
                {
                    // 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.MaxItemValue);
                    }
                    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))
                    {
                        //Console.WriteLine("found landtile {0}/{1} at {2},{3},{4}", ltile.ID, ltile.ID & 0x3fff, x, y, ltile.Z + ltile.Height);
                        locations.Add(new Point3D(x, y, ltile.Z + ltile.Height));
                        continue;
                    }

                    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);
                            locations.Add(new Point3D(x, y, stile.Z + stile.Height));
                            break;
                        }
                    }
                }
            }
        }

        private void FindRegionTileLocations(ref List<Point3D> locations, Region r, List<int> includetilelist, List<int> excludetilelist, TileFlag tileflag)
        {
            if (r == null || r.Area == null) return;

            int count = r.Area.Length;

            if (locations == null) locations = new List<Point3D>();

            // calculate fields of all rectangles (for probability calculating)
            for (int n = 0; n < count; n++)
            {
                object o = r.Area[n];
                int sx = 0;
                int sy = 0;
                int w = -1;
                int h = -1;
                if (o is Rectangle2D)
                {
                    sx = ((Rectangle2D)o).X;
                    sy = ((Rectangle2D)o).Y;
                    w = ((Rectangle2D)o).X + ((Rectangle2D)o).Width;
                    h = ((Rectangle2D)o).Y + ((Rectangle2D)o).Height;
                }
                else
                    if (o is Rectangle3D)
                    {
                        sx = ((Rectangle3D)o).Start.X;
                        sy = ((Rectangle3D)o).Start.Y;
                        w = ((Rectangle3D)o).Width;
                        h = ((Rectangle3D)o).Height;
                    }

                // find all of the valid tile locations in the area
                FindTileLocations(ref locations, r.Map, sx, sy, w, h, includetilelist, excludetilelist, tileflag);
            }


        }

        // 2004.02.08 :: Omega Red
        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++)
            {
                object o = r.Area[i];
                if (o is Rectangle2D)
                {
                    total += (FieldArray[i] = ((Rectangle2D)o).Width * ((Rectangle2D)o).Height);
                }
                else
                    if (o is Rectangle3D)
                    {
                        total += (FieldArray[i] = ((Rectangle3D)o).Width * ((Rectangle3D)o).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)
                {
                    object o = r.Area[i];

                    if (o is Rectangle2D)
                    {
                        Rectangle2D r2d = (Rectangle2D)o;
                        if (r2d.Width >= 0)
                            x = r2d.X + Utility.Random(r2d.Width + 1);

                        if (r2d.Height >= 0)
                            y = r2d.Y + Utility.Random(r2d.Height + 1);

                        break;
                    }
                    else
                        if (o is Rectangle3D)
                        {
                            Rectangle3D r3d = (Rectangle3D)o;
                            if (r3d.Width >= 0)
                                x = r3d.Start.X + Utility.Random(r3d.Width + 1);
                            if (r3d.Height >= 0)
                                y = r3d.Start.Y + Utility.Random(r3d.Height + 1);
                            break;
                        }
                }
            }

            return new Point2D(x, y);
        }

        // used for getting non-mobile spawn positions
        public Point3D GetSpawnPosition(bool requiresurface)
        {
            // no pack spawning
            return GetSpawnPosition(requiresurface, -1, Point3D.Zero, null, null);
        }

        // used for getting mobile spawn positions
        public Point3D GetSpawnPosition(bool requiresurface, Mobile mob)
        {
            // no pack spawning
            return GetSpawnPosition(requiresurface, -1, Point3D.Zero, null, mob);
        }

        // used for getting non-mobile spawn positions
        public Point3D GetSpawnPosition(bool requiresurface, int packrange, Point3D packcoord, List<SpawnPositionInfo> spawnpositioning)
        {
            return GetSpawnPosition(requiresurface, packrange, packcoord, spawnpositioning, null);
        }

        public Point3D GetSpawnPosition(bool requiresurface, int packrange, Point3D packcoord, List<SpawnPositionInfo> spawnpositioning, Mobile mob)
        {
            Map map = Map;

            if (map == null)
                return Location;

            // random positioning by default
            SpawnPositionType positioning = SpawnPositionType.Random;
            Mobile trigmob = null;
            string[] positionargs = null;
            List<int> includetilelist = null;
            List<int> excludetilelist = null;
            // restrictions on tile flags
            TileFlag tileflag = TileFlag.None;
            List<Point3D> locations = null;

            int fillinc = 1;
            int positionrange = 0;
            string prefix = null;
            ArrayList 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;
                    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.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<int>();
                                }

                                // 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<int>();
                                }

                                // 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 Hashtable();
                                }

                                // try to find the waypoint list in the local table
                                WayList = (ArrayList)spawnPositionWayTable[prefix];

                                // no existing list so create a new one
                                if (WayList == null)
                                {
                                    WayList = new ArrayList();

                                    foreach (Item i in World.Items.Values)
                                    {
                                        if (i is WayPoint && i.Name != null && i.Map == Map && i.Name.StartsWith(prefix))
                                        {
                                            // add it to the list of items
                                            WayList.Add(i);
                                        }
                                    }
                                    // add the new list to the local table
                                    spawnPositionWayTable.Add(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);
                }
                else
                    if (positioning == SpawnPositionType.Random)
                    {
                        FindTileLocations(ref locations, this.Map, m_X, m_Y, m_Width, m_Height, includetilelist, excludetilelist, tileflag);
                    }
            }


            // 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 = this.X;
                int y = this.Y;
                int z = Z;

                int defaultZ = this.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))	// 2004.02.08 :: Omega Red
                    {
                        // 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 = this.X + xinc;
                                y = this.Y + yinc;
                                defaultZ = this.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 = (Item)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
                bool fit = false;
                if (requiresurface)
                {
                    fit = CanSpawnMobile(x, y, defaultZ, mob);
                }
                else
                {
                    fit = 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);
                }
                else
                {

                    z = Map.GetAverageZ(x, y);

                    if (requiresurface)
                    {
                        fit = CanSpawnMobile(x, y, z, mob);
                    }
                    else
                    {
                        fit = 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;
            }
            else
            {
                return this.Location;
            }
        }

        public int GetCreatureMax(int index)
        {
            Defrag(false);

            if (m_SpawnObjects == null) return 0;

            return ((SpawnObject)m_SpawnObjects[index]).MaxCount;
        }

        private void DeleteFromList(ArrayList list)
        {
            if (list == null) return;

            foreach (object o in list)
            {
                if (o is Item)
                    ((Item)o).Delete();
                else if (o is Mobile)
                    ((Mobile)o).Delete();
            }
        }

        public void RemoveSpawnObjects()
        {
            if (m_SpawnObjects == null) return;

            Defrag(false);

            ClearTags(true);
            ArrayList deletelist = new ArrayList();
            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);

            ArrayList deletelist = new ArrayList();

            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);
            ArrayList deletelist = new ArrayList();
            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);
            ArrayList deletelist = new ArrayList();
            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);

                }
            }

            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;

                    }

                    ArrayList deletelist = new ArrayList();

                    // 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( TheSpawn.MaxCount < 1 )
                    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();
            }
        }

        #endregion

        #region Object Creation

        public static object CreateObject(Type type, string itemtypestring)
        {
            return CreateObject(type, itemtypestring, true);
        }

        public static object CreateObject(Type type, string itemtypestring, bool requireconstructable)
        {
            // look for constructor arguments to be passed to it with the syntax type,arg1,arg2,.../
            string[] typewordargs = BaseXmlSpawner.ParseObjectArgs(itemtypestring);

            return CreateObject(type, typewordargs, requireconstructable, false);
        }

        public static object CreateObject(Type type, string itemtypestring, bool requireconstructable, bool requireattachable)
        {
            // look for constructor arguments to be passed to it with the syntax type,arg1,arg2,.../
            string[] typewordargs = BaseXmlSpawner.ParseObjectArgs(itemtypestring);

            return CreateObject(type, typewordargs, requireconstructable, requireattachable);
        }

        public static object CreateObject(Type type, string[] typewordargs, bool requireconstructable, bool requireattachable)
        {
            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();

            if (ctors == null) return null;

            // go through all the constructors for this type
            for (int i = 0; i < ctors.Length; ++i)
            {
                ConstructorInfo ctor = ctors[i];

                if (ctor == null) continue;

                // if both requireconstructable and requireattachable are true, then allow either condition
#if(RESTRICTCONSTRUCTABLE)
               if (!(requireconstructable && Add.IsConstructable(ctor,ConstructableAccessLevel)) && !(requireattachable && XmlAttach.IsAttachable(ctor)))
                    continue;
#else
                if (!(requireconstructable && IsConstructable(ctor)) && !(requireattachable && XmlAttach.IsAttachable(ctor)))
                    continue;
#endif

                // check the parameter list of the constructor
                ParameterInfo[] paramList = ctor.GetParameters();

                // and compare with the argument list provided
                if (paramList != null && 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;
        }

        #endregion

        #region Timers


        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)
            {
                Priority = TimerPriority.OneSecond;
            }

            protected override void OnTick()
            {
                // check the sectors

                // check all active players
                if (NetState.Instances != null)
                {
                    foreach (NetState state in NetState.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)
                                {

                                    ArrayList spawnerlist = (ArrayList)GlobalSectorTable[m.Map.MapID][s];
                                    if (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 XmlSpawner m_Spawner;

            public SectorTimer(XmlSpawner spawner, TimeSpan delay)
                : base(delay, delay)
            {
                Priority = TimerPriority.OneSecond;
                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 ArrayList 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 ArrayList();
                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.Now);
                        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.Now + 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.Now + delay;
            if (m_Duration > TimeSpan.FromMinutes(0) || (m_durActivated == true))
            {
                if (m_DurTimer != null)
                    m_DurTimer.Stop();
                m_DurTimer = new InternalTimer(this, delay);
                m_DurTimer.Start();
                m_durActivated = true;
            }
        }

        public void DoTimer3(TimeSpan delay)
        {
            //            if ( !m_proximityActivated )
            //                return;

            m_RefractEnd = DateTime.Now + 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 XmlSpawner m_spawner;

            public InternalTimer(XmlSpawner spawner, TimeSpan delay)
                : base(delay)
            {
                Priority = TimerPriority.OneSecond;
                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 XmlSpawner m_Spawner;

            public SpawnerTimer(XmlSpawner spawner, TimeSpan delay)
                : base(delay)
            {

                // reduce timer priority if spawner is inactivated
                if (spawner.IsInactivated)
                {
                    Priority = TimerPriority.FiveSeconds;
                }
                else
                {
                    Priority = spawner.BasePriority;
                }


                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 XmlSpawner m_spawner;

            public InternalTimer3(XmlSpawner spawner, TimeSpan delay)
                : base(delay)
            {
                Priority = TimerPriority.OneSecond;
                m_spawner = spawner;
            }

            protected override void OnTick()
            {
                if (m_spawner != null && !m_spawner.Deleted)
                {
                    // reenable triggering
                    m_spawner.m_refractActivated = false;
                }
            }
        }
        #endregion

        #region Serialization

        public override void Serialize(GenericWriter writer)
        {
            base.Serialize(writer);

            writer.Write((int)30); // version
            // 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(((SpawnObject)m_SpawnObjects[i]).SpawnsPerTick);
                }
            }
            else
            {
                // empty spawner
                writer.Write((int)0);
            }

            // Version 28
            if (m_SpawnObjects != null)
            {
                for (int i = 0; i < m_SpawnObjects.Count; ++i)
                {
                    // Write the pack range value
                    writer.Write(((SpawnObject)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(((SpawnObject)m_SpawnObjects[i]).Disabled);
                }
            }

            // Version 26
            writer.Write(m_SpawnOnTrigger);
            writer.Write(m_FirstModified);
            writer.Write(m_LastModified);

            // Version 25
            // eliminated the textentrybook serialization (they autodelete on deser now)

            // Version 24
            if (m_SpawnObjects != null)
            {
                for (int i = 0; i < m_SpawnObjects.Count; ++i)
                {
                    SpawnObject so = (SpawnObject)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.WriteItemList(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(((SpawnObject)m_SpawnObjects[i]).RequireSurface);
                }
            }
            // Version 19
            writer.Write(m_ConfigFile);
            writer.Write(m_OnHold);
            writer.Write(m_HoldSequence);
            writer.Write(m_FirstModifiedBy);
            writer.Write(m_LastModifiedBy);
            // 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 ((((BaseXmlSpawner.KeywordTag)(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 ((((BaseXmlSpawner.KeywordTag)(m_KeywordTagList[i])).Flags & BaseXmlSpawner.KeywordFlags.Serialize) != 0)
                {
                    ((BaseXmlSpawner.KeywordTag)(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 = (SpawnObject)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);	// 2004.02.08 :: Omega Red


            // 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.Now);
            if (m_durActivated)
                writer.Write(m_DurEnd - DateTime.Now);
            // 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.Now);

            // 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 = (SpawnObject)m_SpawnObjects[i];

                // Write the type and maximum count
                writer.Write((string)so.TypeName);
                writer.Write((int)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)
                        writer.Write((Item)o);
                    else if (o is Mobile)
                        writer.Write((Mobile)o);
                    else
                    {

                        // if this is a keyword tag then add some more info
                        if (o is BaseXmlSpawner.KeywordTag)
                        {
                            writer.Write(-1 * ((BaseXmlSpawner.KeywordTag)o).Serial - 2);
                        }
                        else
                        {
                            writer.Write(Serial.MinusOne);
                        }
                    }
                }
            }
        }

        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize(reader);

            int version = reader.ReadInt();
            bool haveproximityrange = false;
            bool hasnewobjectinfo = false;
            int tmpSpawnListSize = 0;
            ArrayList tmpSubGroup = null;
            ArrayList tmpSequentialResetTime = null;
            ArrayList tmpSequentialResetTo = null;
            ArrayList tmpKillsNeeded = null;
            ArrayList tmpRequireSurface = null;
            ArrayList tmpRestrictKillsToSubgroup = null;
            ArrayList tmpClearOnAdvance = null;
            ArrayList tmpMinDelay = null;
            ArrayList tmpMaxDelay = null;
            ArrayList tmpNextSpawn = null;
            ArrayList tmpDisableSpawn = null;
            ArrayList tmpPackRange = null;
            ArrayList tmpSpawnsPer = null;

            switch (version)
            {
                case 30:
                    {
                        m_AllowNPCTriggering = reader.ReadBool();
                        goto case 29;
                    }
                case 29:
                    {
                        tmpSpawnListSize = reader.ReadInt();
                        tmpSpawnsPer = new ArrayList(tmpSpawnListSize);
                        for (int i = 0; i < tmpSpawnListSize; ++i)
                        {
                            int spawnsper = reader.ReadInt();

                            tmpSpawnsPer.Add(spawnsper);

                        }
                        goto case 28;
                    }
                case 28:
                    {
                        if (version < 29)
                            tmpSpawnListSize = reader.ReadInt();

                        tmpPackRange = new ArrayList(tmpSpawnListSize);
                        for (int i = 0; i < tmpSpawnListSize; ++i)
                        {
                            int packrange = reader.ReadInt();

                            tmpPackRange.Add(packrange);

                        }
                        goto case 27;
                    }
                case 27:
                    {
                        if (version < 28)
                            tmpSpawnListSize = reader.ReadInt();

                        tmpDisableSpawn = new ArrayList(tmpSpawnListSize);
                        for (int i = 0; i < tmpSpawnListSize; ++i)
                        {
                            bool disablespawn = reader.ReadBool();

                            tmpDisableSpawn.Add(disablespawn);

                        }
                        goto case 26;
                    }
                case 26:
                    {
                        m_SpawnOnTrigger = reader.ReadBool();
                        m_FirstModified = reader.ReadDateTime();
                        m_LastModified = reader.ReadDateTime();
                        goto case 25;
                    }
                case 25:
                    {
                        goto case 24;
                    }
                case 24:
                    {
                        if (version < 27)
                            tmpSpawnListSize = reader.ReadInt();
                        tmpRestrictKillsToSubgroup = new ArrayList(tmpSpawnListSize);
                        tmpClearOnAdvance = new ArrayList(tmpSpawnListSize);
                        tmpMinDelay = new ArrayList(tmpSpawnListSize);
                        tmpMaxDelay = new ArrayList(tmpSpawnListSize);
                        tmpNextSpawn = new ArrayList(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.ReadItemList();
                        }
                        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.ReadMobile();
                        goto case 21;
                    }
                case 21:
                    {
                        m_DespawnTime = reader.ReadTimeSpan();
                        goto case 20;
                    }
                case 20:
                    {
                        if (version < 24)
                            tmpSpawnListSize = reader.ReadInt();
                        tmpRequireSurface = new ArrayList(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();
                        m_FirstModifiedBy = reader.ReadString();
                        m_LastModifiedBy = reader.ReadString();
                        // deserialize the keyword tag list
                        int tagcount = reader.ReadInt();
                        m_KeywordTagList = new ArrayList(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:
                    {
                        if (version < 25)
                        {
                            // the textentrybooks are deleted on deserialization so no need to track them
                            reader.ReadItem();
                        }
                        goto case 16;
                    }
                case 16:
                    {
                        hasnewobjectinfo = true;
                        m_SequentialSpawning = reader.ReadInt();
                        TimeSpan seqdelay = reader.ReadTimeSpan();
                        m_SeqEnd = DateTime.Now + seqdelay;
                        if (version < 20)
                        {
                            tmpSpawnListSize = reader.ReadInt();
                        }
                        tmpSubGroup = new ArrayList(tmpSpawnListSize);
                        tmpSequentialResetTime = new ArrayList(tmpSpawnListSize);
                        tmpSequentialResetTo = new ArrayList(tmpSpawnListSize);
                        tmpKillsNeeded = new ArrayList(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();	// 2004.02.08 :: Omega Red
                        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.ReadItem();
                        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.ReadItem();
                        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 == true)
                        {
                            TimeSpan delay = reader.ReadTimeSpan();
                            DoTimer3(delay);
                        }
                        if (m_durActivated == true)
                        {
                            TimeSpan delay = reader.ReadTimeSpan();
                            DoTimer2(delay);
                        }
                        goto case 3;
                    }
                case 3:
                    {
                        m_ShowContainerStatic = reader.ReadItem() as Static;
                        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 (m_Name != null && m_Name != String.Empty) Name = m_Name;
                        m_X = reader.ReadInt();
                        m_Y = reader.ReadInt();
                        m_Width = reader.ReadInt();
                        m_Height = reader.ReadInt();
                        if (m_Width == m_Height)
                            m_SpawnRange = m_Width / 2;
                        else
                            m_SpawnRange = -1;
                        if (!haveproximityrange)
                        {
                            m_ProximityRange = -1;
                        }
                        m_WayPoint = reader.ReadItem() as WayPoint;
                        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 == true)
                        {
                            TimeSpan delay = reader.ReadTimeSpan();
                            DoTimer(delay);
                        }

                        // Read in the size of the spawn object list
                        int SpawnListSize = reader.ReadInt();
                        m_SpawnObjects = new ArrayList(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);
                            // does it have a substitution that might change its validity?
                            // if so then let it go

                            if (typeName == null || ((SpawnerType.GetType(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);

                                this.status_str = "invalid type: " + typeName;
                            }

                            // Read in the number of spawns already
                            int SpawnedCount = reader.ReadInt();

                            TheSpawnObject.SpawnedObjects = new ArrayList(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);

                                    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 = (SpawnObject)m_SpawnObjects[i];

                                so.SubGroup = (int)tmpSubGroup[i];
                                so.SequentialResetTime = (double)tmpSequentialResetTime[i];
                                so.SequentialResetTo = (int)tmpSequentialResetTo[i];
                                so.KillsNeeded = (int)tmpKillsNeeded[i];
                                if (version > 19)
                                    so.RequireSurface = (bool)tmpRequireSurface[i];
                                bool restrictkills = false;
                                bool clearadvance = true;
                                double mind = -1;
                                double maxd = -1;
                                DateTime nextspawn = DateTime.MinValue;
                                if (version > 23)
                                {
                                    restrictkills = (bool)tmpRestrictKillsToSubgroup[i];
                                    clearadvance = (bool)tmpClearOnAdvance[i];
                                    mind = (double)tmpMinDelay[i];
                                    maxd = (double)tmpMaxDelay[i];
                                    nextspawn = (DateTime)tmpNextSpawn[i];
                                }
                                so.RestrictKillsToSubgroup = restrictkills;
                                so.ClearOnAdvance = clearadvance;
                                so.MinDelay = mind;
                                so.MaxDelay = maxd;
                                so.NextSpawn = nextspawn;

                                bool disablespawn = false;
                                if (version > 26)
                                {
                                    disablespawn = (bool)tmpDisableSpawn[i];
                                }
                                so.Disabled = disablespawn;

                                int packrange = -1;
                                if (version > 27)
                                {
                                    packrange = (int)tmpPackRange[i];
                                }
                                so.PackRange = packrange;

                                int spawnsper = 1;
                                if (version > 28)
                                {
                                    spawnsper = (int)tmpSpawnsPer[i];
                                }
                                so.SpawnsPerTick = spawnsper;

                            }
                        }

                        break;
                    }
            }
        }

        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();
        }

        #endregion

        #region Spawn classes

        public class SpawnObject
        {
            private string m_TypeName;
            private int m_MaxCount;
            private int m_SubGroup;
            private int m_SequentialResetTo;
            private int m_KillsNeeded;
            private bool m_RestrictKillsToSubgroup = false;
            private bool m_ClearOnAdvance = true;
            private double m_MinDelay = -1;
            private double m_MaxDelay = -1;
            private int m_SpawnsPerTick = 1;
            private bool m_Disabled = false;
            private int m_PackRange = -1;
            private bool m_Ignore = false;
            // temporary variable used to calculate weighted spawn probabilities
            public bool Available;


            public ArrayList 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 { return m_TypeName; } set { m_TypeName = value; } }
            public int MaxCount
            {
                get
                {
                    if (Disabled)
                    {
                        return 0;
                    }
                    else
                    {
                        return m_MaxCount;
                    }
                }
                set
                {
                    m_MaxCount = value;
                }
            }
            public int ActualMaxCount { get { return m_MaxCount; } set { m_MaxCount = value; } }
            public int SubGroup { get { return m_SubGroup; } set { m_SubGroup = value; } }
            public int SpawnsPerTick { get { return m_SpawnsPerTick; } set { m_SpawnsPerTick = value; } }
            public int SequentialResetTo { get { return m_SequentialResetTo; } set { m_SequentialResetTo = value; } }
            public int KillsNeeded { get { return m_KillsNeeded; } set { m_KillsNeeded = value; } }
            public bool RestrictKillsToSubgroup { get { return m_RestrictKillsToSubgroup; } set { m_RestrictKillsToSubgroup = value; } }
            public bool ClearOnAdvance { get { return m_ClearOnAdvance; } set { m_ClearOnAdvance = value; } }
            public double MinDelay { get { return m_MinDelay; } set { m_MinDelay = value; } }
            public double MaxDelay { get { return m_MaxDelay; } set { m_MaxDelay = value; } }
            public bool Disabled { get { return m_Disabled; } set { m_Disabled = value; } }
            public bool Ignore { get { return m_Ignore; } set { m_Ignore = value; } }
            public int PackRange { get { return m_PackRange; } set { m_PackRange = value; } }


            // 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] as SpawnObject;
                            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 ArrayList();
            }

            public SpawnObject(string name, int maxamount)
            {
                TypeName = name;
                MaxCount = maxamount;
                SubGroup = 0;
                SequentialResetTime = 0;
                SequentialResetTo = 0;
                KillsNeeded = 0;
                RestrictKillsToSubgroup = false;
                ClearOnAdvance = true;
                SpawnedObjects = new ArrayList();
            }

            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 ArrayList();
            }

            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
                ArrayList NewSpawnObjects = new ArrayList();

                if (ObjectList != null && ObjectList.Length > 0)
                {
                    // 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 (System.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 (SpawnObject[])NewSpawnObjects.ToArray(typeof(SpawnObject));
            }



            internal static SpawnObject[] LoadSpawnObjectsFromString2(string ObjectList)
            {
                // Clear the spawn object list
                ArrayList NewSpawnObjects = new ArrayList();

                // 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 (ObjectList != null && ObjectList.Length > 0)
                {
                    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 (SpawnObject[])NewSpawnObjects.ToArray(typeof(SpawnObject));
            }
        }


        #endregion

    }
}

