/**********************************************************
RunUO 2.0 AoV C# script file
Official Age of Valor Script :: www.uovalor.com
Last modified by 
Filepath: scripts\Engines\Spawner\Spawner.cs
Lines of code: 1081
***********************************************************/


using System;
using System.IO;
using System.Collections;
using Server;
using Server.Items;
using System.Collections.Generic;
using System.Reflection;
using Server.Mobiles;

namespace Server.Commands
{
	public class globalrespawn
	{
		public static void Initialize()
		{
			CommandSystem.Register( "globalrespawn", AccessLevel.Administrator, new CommandEventHandler( globalrespawn_OnCommand ),true );
		}

		[Usage( "globalrespawn (int maxmobs)" )]
		[Description( "Globally speeds up all spawners so they fully respawn.  This may cause lag" )]
		private static void globalrespawn_OnCommand( CommandEventArgs e )
		{		
			int max=0;
			if ( e.Length >= 1 )max = e.GetInt32( 0 );
			
			bool forceglobaltimers=(Server.Mobiles.Spawner.fastspawnmodemax<=0);
			
			Mobile from = e.Mobile;
			
			if(max>0)
			{		
			Server.Mobiles.Spawner.fastspawnmode=true;
			Server.Mobiles.Spawner.fastspawnmodemax=max;		
			
			from.SendMessage("Speeding up spawns up to a max of {0} mobs",max);	
			}
			else
			{
			Server.Mobiles.Spawner.fastspawnmode=false;
			from.SendMessage("Spawn speeds now back to normal");
			}
			
			//get all spawners in world and reset their next spawn date
			
			if(forceglobaltimers)
			{
					
				foreach ( Item i in World.Items.Values )
				{
					if(i is Spawner)
					{
						Spawner spawner = i as Spawner;
						
						if(spawner.Running)
						{
						spawner.LastSpawnDate=DateTime.Now; //to make the cycle delay go faster (initially is slower if last spawn was too long ago)
						spawner.Stop();
						spawner.Start();
						}					
					}
				}
			
			}					
		}	
	}	
	
	
	public class globalrespawnstatus
	{
		public static void Initialize()
		{
			CommandSystem.Register( "globalrespawnstatus", AccessLevel.Administrator, new CommandEventHandler(globalrespawnstatus_OnCommand ),true );
		}

		[Usage( "globalrespawnstatus" )]
		[Description( "checks status of global respawn" )]
		private static void globalrespawnstatus_OnCommand( CommandEventArgs e )
		{		
		Mobile from = e.Mobile;

		from.SendMessage("Spawning {0} mobs using fastspawn rate",Server.Mobiles.Spawner.fastspawnmodemax);				
		}	
	}	
}



namespace Server.Mobiles
{
	public class Spawner : Item
	{
		public static bool fastspawnmode=false;
		public static int fastspawnmodemax;
	
		private int m_Team;
		private int m_HomeRange;
		private int m_WalkingRange = -1; 
		private int m_Count;
		private TimeSpan m_MinDelay;
		private TimeSpan m_MaxDelay;
		private List<string> m_CreaturesName;
		private ArrayList m_Creatures;
		private DateTime m_End;
		private InternalTimer m_Timer;
		private bool m_Running;
		private bool m_Group;
		private bool m_ignorehome;
		private WayPoint m_WayPoint;
		
		private TimeSpan m_cycledelay=TimeSpan.FromSeconds(10); //delay in seconds between each count check - higher it is, the more efficient, but less acurate.
		private bool m_felandtram=false; //if true, will duplicate spawn in fel and trammel
		private bool m_smartspawn=false; //if set to yes, try to balance spawning locations

		public bool IsFull{ get{ return ( m_Creatures != null && m_Creatures.Count >= m_Count ); } }
		
		private DateTime m_nextspawndate; //next time we spawn - not serialized
		private TimeSpan m_nextspawndelay; //delay till next spawn - not serialized
		bool totalrespawn=false; //if set to true, ignores timers and just respawns everything
		private bool m_IgnoreCanSpawn; //if set to true, will not check if the area is spawnable, will just spawn anyway (for water)
		private int m_TriggerDistance=-1; //if set to above -1, will only trigger when someone walks on it.
		
		private DateTime m_LastDefrag; //last time we ran defrag
		private DateTime m_LastSpawnDate; //last time we spawed
		
		private static int ActSinceLimiter; //how many spawns since the limiter date
		private static DateTime LimiterDate; //used to globally limit spawn rate to avoid being too hard on server
		
		[CommandProperty( AccessLevel.GameMaster )]
		public DateTime LastSpawnDate
		{
		get{return m_LastSpawnDate;}
		set{m_LastSpawnDate=value;}
		}
		
		
		public List<string> CreaturesName
		{
			get { return m_CreaturesName; }
			set
			{
				m_CreaturesName = value;
				if ( m_CreaturesName.Count < 1 )
					Stop();

				InvalidateProperties();
			}
		}
		
		//props creature names  --------------------------------
		
		
		//helper functions for spawn props display
		
		//returns empty string if this entry is empty, ortherwise it returns some weird "system.reflection" thing which causes a crash
		private string FormatPropsCreature(int entrynum)
		{
		if(m_CreaturesName.Count<(entrynum+1))return "";
		return (string)CreaturesName[entrynum]; 
		}
		
		private string ValidatePropsCreature(string creature)
		{
		Type type = SpawnerType.GetType( creature );
		
		if(type==null)return "";
		else return creature;		
		}	
		
		
		//props function entry 0		
		[CommandProperty( AccessLevel.GameMaster )]
		public string Spawn1
		{
			get {return FormatPropsCreature(0);	}
			set	
			{
			string thevalue=ValidatePropsCreature((string)value);
			CreaturesName[0] =  (string)thevalue; 	
			}
		}		
		//props function entry 1		
		[CommandProperty( AccessLevel.GameMaster )]
		public string Spawn2
		{
			get {return FormatPropsCreature(1);	}
			set	
			{
			string thevalue=ValidatePropsCreature((string)value);
			CreaturesName[1] =  (string)thevalue; 	
			}
		}	
		//props function entry 2		
		[CommandProperty( AccessLevel.GameMaster )]
		public string Spawn3
		{
			get {return FormatPropsCreature(2);	}
			set	
			{
			string thevalue=ValidatePropsCreature((string)value);
			CreaturesName[2] =  (string)thevalue; 	
			}
		}	
		//props function entry 3		
		[CommandProperty( AccessLevel.GameMaster )]
		public string Spawn4
		{
			get {return FormatPropsCreature(3);	}
			set	
			{
			string thevalue=ValidatePropsCreature((string)value);
			CreaturesName[3] =  (string)thevalue; 	
			}
		}	
		//props function entry 4		
		[CommandProperty( AccessLevel.GameMaster )]
		public string Spawn5
		{
			get {return FormatPropsCreature(4);	}
			set	
			{
			string thevalue=ValidatePropsCreature((string)value);
			CreaturesName[4] =  (string)thevalue; 	
			}
		}	
		//we could show em all but 5 should really do for most cases
		
		
		//end props creature names   --------------------------------
		

        public override void OnAfterDuped( Item newItem )
        {
            Spawner s = newItem as Spawner;

            if ( s == null )
                return;

			s.m_CreaturesName = new List<string>( m_CreaturesName );
        }
		
		[CommandProperty( AccessLevel.GameMaster )]
		public int Count
		{
			get { return m_Count; }
			set { m_Count = value; InvalidateProperties(); }
		}

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

		[CommandProperty( AccessLevel.GameMaster )]
		public bool Running
		{
			get { return m_Running; }
			set
			{
				if ( value )
					Start();
				else
					Stop();

				InvalidateProperties();
			}
		}

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

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

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

		[CommandProperty( AccessLevel.GameMaster )]
		public TimeSpan MinDelay
		{
			get { return m_MinDelay; }
			set { m_MinDelay = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public TimeSpan MaxDelay
		{
			get { return m_MaxDelay; }
			set { m_MaxDelay = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public DateTime NextSpawnDate
		{
			get
			{
				return m_nextspawndate;
			}
		}
		
		[CommandProperty( AccessLevel.GameMaster )]
		public TimeSpan NextSpawnDelay
		{
			get
			{
				return m_nextspawndelay;
			}
		}		
		
	/*	[CommandProperty( AccessLevel.GameMaster )]
		public TimeSpan NextSpawnCheck
		{
			get
			{
				if ( m_Running )
					return m_End - DateTime.Now;
				else
					return TimeSpan.FromSeconds( 0 );
			}
			set
			{
				Start();
				DoTimer( value );
			}
		}	*/	

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

		[CommandProperty( AccessLevel.GameMaster )]
		public TimeSpan CycleDelay
		{
			get { //if its been an hour since last respawn, we make cycledelay slower, but less then 25 minutes
				if(fastspawnmode && m_cycledelay>TimeSpan.FromMinutes(1))return TimeSpan.FromSeconds(30);			
			
				if(LastSpawnDate+TimeSpan.FromHours(1)<DateTime.Now)
				{
					TimeSpan tmp=TimeSpan.FromSeconds(m_cycledelay.TotalSeconds*2)+TimeSpan.FromMinutes(5);

					if(tmp<TimeSpan.FromMinutes(25))return tmp;
				}
			
				return m_cycledelay;
			}
		}
		
		[CommandProperty( AccessLevel.GameMaster )]
		public bool FelandTram
		{
			get { return m_felandtram; }
			set { m_felandtram = value; InvalidateProperties(); }
		}		

		[CommandProperty( AccessLevel.GameMaster )]
		public bool SmartSpawn
		{
			get { return m_smartspawn; }
			set { m_smartspawn = value; InvalidateProperties(); }
		}	
		
		[CommandProperty( AccessLevel.GameMaster )]
		private int ToSpawn
		{
			get 
			{ 
			if(m_felandtram && !totalrespawn)return m_Count*2;
			else return m_Count;			
			}

		}		

		[CommandProperty( AccessLevel.GameMaster )]
		public bool IgnoreCanSpawn
		{
			get { return m_IgnoreCanSpawn; }
			set { m_IgnoreCanSpawn = value; InvalidateProperties(); }
		}			
		
		[CommandProperty( AccessLevel.GameMaster )]
		public int TriggerDistance
		{
			get { return m_TriggerDistance; }
			set { m_TriggerDistance = value;  }
		}		

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

		[Constructable]
		public Spawner( int amount, int minDelay, int maxDelay, int team, int homeRange, string creatureName ) : base( 0x1f13 )
		{
			List<string> creaturesName = new List<string>();
			creaturesName.Add( creatureName.ToLower() );
			InitSpawn( amount, TimeSpan.FromMinutes( minDelay ), TimeSpan.FromMinutes( maxDelay ), team, homeRange, creaturesName );
		}

		[Constructable]
		public Spawner( string creatureName ) : base( 0x1f13 )
		{
			List<string> creaturesName = new List<string>();
			creaturesName.Add( creatureName.ToLower() );
			InitSpawn( 1, TimeSpan.FromMinutes( 2 ), TimeSpan.FromMinutes( 5 ), 0, 4, creaturesName );
		}

		[Constructable]
		public Spawner() : base( 0x1f13 )
		{
			List<string> creaturesName = new List<string>();
			InitSpawn( 1, TimeSpan.FromMinutes( 2 ), TimeSpan.FromMinutes( 5 ), 0, 4, creaturesName );
		}

		public Spawner( int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, List<string> creaturesName )
			: base( 0x1f13 )
		{
			InitSpawn( amount, minDelay, maxDelay, team, homeRange, creaturesName );
		}

		public override string DefaultName
		{
			get { return "Spawner"; }
		}

		public void InitSpawn( int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, List<string> creaturesName )
		{
			ItemID = 3948;
			Hue = 1153;
		
			Visible = false;
			Movable = false;
			m_Running = true;
			m_Group = false;
			m_MinDelay = minDelay;
			m_MaxDelay = maxDelay;
			m_Count = amount;
			m_Team = team;
			m_HomeRange = homeRange;
			m_CreaturesName = creaturesName;
			LastSpawnDate=DateTime.Now;
			m_Creatures = new ArrayList();
			DoTimer( TimeSpan.FromSeconds(1) );
		}
			
		public Spawner( Serial serial ) : base( serial )
		{
		}

		public override void OnDoubleClick( Mobile from )
		{
			if ( from.AccessLevel < AccessLevel.GameMaster )
				return;

			SpawnerGump g = new SpawnerGump( this );
			from.SendGump( g );
		}

		public override void GetProperties( ObjectPropertyList list )
		{
			base.GetProperties( list );

			if ( m_Running )
			{
				list.Add( 1060742 ); // active

				list.Add( 1060656, m_Count.ToString() ); // amount to make: ~1_val~
				list.Add( 1061169, m_HomeRange.ToString() ); // range ~1_val~
            	list.Add( 1060658, "walking range\t{0}", m_WalkingRange ); // ~1_val~: ~2_val~ 

				list.Add( 1060659, "group\t{0}", m_Group ); // ~1_val~: ~2_val~
				list.Add( 1060660, "team\t{0}", m_Team ); // ~1_val~: ~2_val~
				list.Add( 1060661, "speed\t{0} to {1}", m_MinDelay, m_MaxDelay ); // ~1_val~: ~2_val~

				for ( int i = 0; i < 2 && i < m_CreaturesName.Count; ++i )
					list.Add( 1060662 + i, "{0}\t{1}", m_CreaturesName[i], CountCreatures( (string)m_CreaturesName[i] ) );
			}
			else
			{
				list.Add( 1060743 ); // inactive
			}
		}

		public override void OnSingleClick( Mobile from )
		{
			base.OnSingleClick( from );

			if ( m_Running )
				LabelTo( from, "[Running]" );
			else
				LabelTo( from, "[Off]" );
		}

		public void Start()
		{
			if ( !m_Running )
			{
				if ( m_CreaturesName.Count > 0 )
				{
					m_Running = true;
					DoTimer();
				}
			}
		}

		public void Stop()
		{
			if ( m_Running )
			{
				m_Timer.Stop();
				m_Running = false;
			}
		}
		
		public override bool HandlesOnMovement{ get{ return TriggerDistance>=0; } }

		public override void OnMovement( Mobile m, Point3D oldLocation )
		{
		if(TriggerDistance<0 || m.AccessLevel>AccessLevel.Player || !m.Alive || m.IsDeadBondedPet)return;
		
			if ( m.Player && Utility.InRange( m.Location, Location, TriggerDistance ) && !Utility.InRange( oldLocation, Location, TriggerDistance ) )
			{
			if(NextSpawnDate<DateTime.Now)	Defrag();			

				if(m_Group && m_Creatures.Count<=0)Respawn();
				else if(!m_Group)
				Spawn();				
			}
		}
		
		//this variables used when felandtram flag turned on
		private int felmobs;
		private int trammobs;

		private void SetCreatureMap(object o)
		{		
			if(o is Mobile)
			{
			Mobile m = (Mobile)o;
			if(m.Map==Map.Trammel)trammobs++;
			else if(m.Map==Map.Felucca)felmobs++;
			}
			else if (o is Item)
			{
			Item i = (Item)o;
			if(i.Map==Map.Trammel)trammobs++;
			else if(i.Map==Map.Felucca)felmobs++;			
			}
		}		
		
		public void Defrag()
		{		
			Debugger.Write("spawner","{0} defrag",this.Serial);
		
			bool removed = false;
			felmobs=0;
			trammobs=0;
			

			for ( int i = 0; i < m_Creatures.Count; ++i )
			{
				object o = m_Creatures[i];

				if ( o is Item )
				{
					Item item = (Item)o;
					
					if(RootParentEntity !=null && RootParentEntity is Container && item.RootParentEntity!=RootParentEntity)
					{
					m_Creatures.RemoveAt( i );
					--i;
					removed = true;
					}					
					else if ( item.Deleted || item.Parent != null && item.RootParentEntity!=RootParentEntity)
					{
						m_Creatures.RemoveAt( i );
						--i;
						removed = true;
					}
					else if(m_felandtram)SetCreatureMap(o);
					
				}
				else if ( o is Mobile )
				{
					Mobile m = (Mobile)o;
					
					if ( m.Deleted )
					{
						m_Creatures.RemoveAt( i );
						--i;
						removed = true;
					}
					else if ( m is BaseCreature )
					{
						if ( ((BaseCreature)m).Controlled || ((BaseCreature)m).IsStabled )
						{
							m_Creatures.RemoveAt( i );
							--i;
							removed = true;
						}
						else if(m_felandtram)SetCreatureMap(o);
					}
					
				}
				else
				{
					m_Creatures.RemoveAt( i );
					--i;
					removed = true;
				}
			}

			if ( removed )
				InvalidateProperties();
				
			m_LastDefrag=DateTime.Now;
							
			Debugger.Write("spawner","{2} Defrag - trammobs: {0} felmobs: {1}",trammobs,felmobs,this.Serial);
		}

		public void OnTick()
		{
			totalrespawn=false;
			
			Debugger.Write("spawner","{0} OnTick",this.Serial);
			


			if ( m_Group )
			{
				Defrag();

				if  ( m_Creatures.Count == 0 )
				{
					Respawn();
				}
				else
				{
					return;
				}
			}
			else
			{
				Spawn();
			}
			
		DoTimer();
		}
		
		public void Respawn()
		{
		totalrespawn=true;
			RemoveCreatures();


			m_nextspawndate=DateTime.Now;
			
			//Note: the counter is to ensure that if the array can't fully spawn, then it will still stop (ex: no valid locations)
			for (int i=0; m_Creatures.Count < ToSpawn && i<ToSpawn;i++)
				Spawn();
				
		totalrespawn=false;
		
		Defrag();
		}
		

		

		public void Spawn()
		{
			Map map = Map;				

			if ( m_CreaturesName.Count <= 0 )
				return;				
				
			if(m_LastDefrag+TimeSpan.FromSeconds(2)<DateTime.Now && !totalrespawn)Defrag();
				
			if ( m_Creatures.Count >= ToSpawn && !totalrespawn)
			{
				Debugger.Write("spawner","{0} time push",this.Serial);
				
				if(CycleDelay>m_nextspawndelay)m_nextspawndelay=CycleDelay;
				
				m_nextspawndate = DateTime.Now+m_nextspawndelay; 	//time push so next spawn is AT LEAST delayed by mindelay.  Posibility of surpassing maxdelay depending on cycle delay			
				return;
			}

			if(DateTime.Now<m_nextspawndate && !totalrespawn && !fastspawnmode)	return; 
			
			//at this point, we need to spawn, so we'll set the date anyway, even though theres a chance it wont spawn:

			Debugger.Write("spawner","{0} needs to spawn...",this.Serial);
			

			
			//cpu saving stuff:  (limits spawn rate globally)
			
			LastSpawnDate=DateTime.Now;	
			
			//Console.WriteLine("{0} Limiterdate: {1} acts: {2}",this.Serial,LimiterDate,ActSinceLimiter);
			Debugger.Write("spawner","{0} Limiterdate: {1} acts: {2}",this.Serial,LimiterDate,ActSinceLimiter);
			

			if(LimiterDate+TimeSpan.FromSeconds(1)>DateTime.Now && !fastspawnmode)
			{
				Debugger.Write("spawner","{0} checking if global delay allows spawning...",this.Serial);
				
				if(ActSinceLimiter>6 && !totalrespawn)	return;
				else ActSinceLimiter++;		

				Debugger.Write("spawner","{0} ok",this.Serial);			
			}			
			else ActSinceLimiter=0;
			LimiterDate=DateTime.Now;
			
	

				
			
			//set next spawn date
			
			int minSeconds = (int)m_MinDelay.TotalSeconds;
			int maxSeconds = (int)m_MaxDelay.TotalSeconds;
			m_nextspawndelay = fastspawnmode ? TimeSpan.FromSeconds(1) : TimeSpan.FromSeconds( Utility.RandomMinMax( minSeconds, maxSeconds ) );			
			
			if(CycleDelay>m_nextspawndelay)m_nextspawndelay=CycleDelay;
			
			m_nextspawndate = DateTime.Now + m_nextspawndelay;
			
		
		
			if(fastspawnmode)
			{
			fastspawnmodemax--;
			if(fastspawnmodemax<=0)fastspawnmode=false;
			}
		
			int amttospawn=(ToSpawn-m_Creatures.Count)/3;
			if(amttospawn<1)amttospawn=1;
			if(amttospawn>10)	amttospawn=1+ Utility.Random(amttospawn);
			if(amttospawn>100)amttospawn=100;
			
			if(amttospawn<=0)amttospawn=1;
			
			Debugger.Write("spawner","{0} will spawn {1}",this.Serial,amttospawn);
			
			bool spawned=false;
			
			for(int spawni=0;spawni<amttospawn;spawni++)
			{
				Debugger.Write("spawner","{0} spawnloop",this.Serial);
				//decide which map to spawn on
				if(m_felandtram && !totalrespawn)
				{
					if(felmobs<trammobs)
					{
					//felmobs++;
					map=Map.Felucca;
					}
					else
					{
					//trammobs++;					
					map=Map.Trammel;		
					}
				}				
			

				//find spawn location
				Point3D loc = GetSpawnPosition(map);
				if(SmartSpawn && loc==this.Location && this.HomeRange>5)break; 
				Debugger.Write("spawner","{0} spawning...",this.Serial);
				
				spawned=true;
			
			
				Type type = SpawnerType.GetType( (string)m_CreaturesName[Utility.Random(m_CreaturesName.Count)] );

				if ( type != null )
				{
					try
					{
						object o = Activator.CreateInstance( type );

						if ( o is Mobile )
						{
							Mobile m = (Mobile)o;

							m_Creatures.Add( m );
							

							loc = (( m is BaseVendor && (!(m is BaseHealer))) ? this.Location : loc );

							m.OnBeforeSpawn( loc, map );
							InvalidateProperties();

							
							if(RootParentEntity !=null)
							Console.WriteLine("Warning: Spawner {0} attempting to spawn creature inside container {1}",this.Serial,ParentEntity.Serial); 
							else m.MoveToWorld( loc, map );

							if ( m is BaseCreature)
							{
								BaseCreature c = (BaseCreature)m;
								
								if( m_WalkingRange >= 0  && !m_ignorehome)
									c.RangeHome = m_WalkingRange;
								else if(!m_ignorehome)
									c.RangeHome = m_HomeRange;

								c.CurrentWayPoint = m_WayPoint;

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

								if(!m_ignorehome)c.Home = this.Location;
							}

							m.OnAfterSpawn();
						}
						else if ( o is Item )
						{
							Item item = (Item)o;

							m_Creatures.Add( item );

							if(RootParentEntity !=null)
							{
							Container tmp = RootParentEntity as Container;
							tmp.DropItem(item);						
							}
							else
							{
							item.OnBeforeSpawn( loc, map );

							item.MoveToWorld( loc, map );
							}
							
							if(item is Spellbook) //fill spawned spellbooks
							{
							Spellbook.FillSpellBook((Spellbook)item);						
							}
							
							item.OnAfterSpawn();
							
							InvalidateProperties();
						}
					}
					catch
					{
					}
				}				
			}
			
			if(!spawned)m_nextspawndate=DateTime.Now+TimeSpan.FromSeconds(5);
			else Debugger.Write("spawner","{0} spawned!",this.Serial);
			
		}
		
		
		//loops through each mob in the spawner to compare points to proposed new location and returns wether this point is too close to an existing mob location.  This is used for smart spawning
		private bool IsTooClose(int x, int y,Map map)
		{
		Point2D a = new Point2D(x,y);
		
		double toocloserange=(this.HomeRange*2)/(Math.Sqrt(m_Creatures.Count)+2);
				
			for(int i=0;i<m_Creatures.Count;i++)
			{
				object o = m_Creatures[i];
				
				int cx,cy;				
				
				if ( o is Item )
				{
				cx=((Item)o).X;
				cy=((Item)o).Y;
				if(((Item)o).Map!=map)continue;
				}
				else if ( o is BaseCreature )
				{
				cx=((BaseCreature)o).X;
				cy=((BaseCreature)o).Y;
				if(((BaseCreature)o).Map!=map)continue;
				}
				else continue;
				
			Point2D b = new Point2D(cx,cy);			
			
			//check if both points are close too each other
			if(a.GetDistanceFrom(b)<toocloserange)return true;
			}	
		
		return false;
		}
		
		
		

		public Point3D GetSpawnPosition(Map map)
		{
			if ( map == null )
				return Location;
				
			IsTooClose(X,Y,map);
				
			//vars used for smart spawning
			bool UseSmartSpawn=(SmartSpawn && m_Creatures.Count>2 && !fastspawnmode);
			

			// Try  to find a Spawnable location.
			for ( int i = 0; i < (UseSmartSpawn ? 70 : 50); i++ )  //default is to try 10 times
			{
				int x = Location.X + (Utility.Random( (m_HomeRange * 2) + 1 ) - m_HomeRange);
				int y = Location.Y + (Utility.Random( (m_HomeRange * 2) + 1 ) - m_HomeRange);
				int z = Map.GetAverageZ( x, y );

				Point3D dest=new Point3D(this.X,this.Y,this.Z);
				
				if ( Map.CanSpawnMobile( new Point2D( x, y ), this.Z ) || m_IgnoreCanSpawn)
					dest =  new Point3D( x, y, this.Z );
				else if ( Map.CanSpawnMobile( new Point2D( x, y ), z ) || m_IgnoreCanSpawn)
					dest = new Point3D( x, y, z );
				else continue;

			
				if(UseSmartSpawn)
				{
					if(i>=30)UseSmartSpawn=false; //fall over to regular system after too many tries
					
					if(IsTooClose(dest.X,dest.Y,map))continue;
					return dest;
				}
				else return dest;
				
				
			}

			return Location;   //default: retrn this.Location
		}

		public void DoTimer()
		{
			if ( !m_Running )
				return;
			
		
		DoTimer( CycleDelay + TimeSpan.FromSeconds(1+Utility.Random(10)) );
		}

		public void DoTimer( TimeSpan delay )
		{
			if ( !m_Running )
				return;

			m_End = DateTime.Now + delay;

			//would it be more efficient to keep same timer instead of starting another one each time?  can this cause a memory leak or unneeded processing? needs to be looked into further.
			
			if ( m_Timer != null )
				m_Timer.Stop();


			m_Timer = new InternalTimer( this, delay );
			m_Timer.Start();
		}

		private class InternalTimer : Timer
		{
			private Spawner m_Spawner;

			public InternalTimer( Spawner spawner, TimeSpan delay ) : base( delay )
			{
				Priority=ComputePriority( delay );
				if(Priority<TimerPriority.OneSecond)Priority=TimerPriority.OneSecond;

				m_Spawner = spawner;
			}

			protected override void OnTick()
			{
				if ( m_Spawner != null )
					if ( !m_Spawner.Deleted )
						m_Spawner.OnTick();
			}
		}

		public int CountCreatures( string creatureName )
		{
			Defrag();

			int count = 0;

			for ( int i = 0; i < m_Creatures.Count; ++i )
				if ( Insensitive.Equals( creatureName, m_Creatures[i].GetType().Name ) )
					++count;

			return count;
		}

		public void RemoveCreatures( string creatureName )
		{
			Defrag();

			creatureName = creatureName.ToLower();

			for ( int i = 0; i < m_Creatures.Count; ++i )
			{
				object o = m_Creatures[i];

				if ( Insensitive.Equals( creatureName, o.GetType().Name ) )
				{
					if ( o is Item )
						((Item)o).Delete();
					else if ( o is Mobile )
						((Mobile)o).Delete();
				}
			}
			
			felmobs=0;
			trammobs=0;

			InvalidateProperties();
		}
		
		public void RemoveCreatures()
		{
			Defrag();

			for ( int i = 0; i < m_Creatures.Count; ++i )
			{
				object o = m_Creatures[i];

				if ( o is Item )
					((Item)o).Delete();
				else if ( o is Mobile )
					((Mobile)o).Delete();
			}

			InvalidateProperties();
		}
		
		public void BringToHome()
		{
			Defrag();

			for ( int i = 0; i < m_Creatures.Count; ++i )
			{
				object o = m_Creatures[i];

				if ( o is Mobile )
				{
					Mobile m = (Mobile)o;

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

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

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

			RemoveCreatures();
			if ( m_Timer != null )
				m_Timer.Stop();
		}
		
		public override void Serialize( GenericWriter writer )
		{
			base.Serialize( writer );

			writer.Write( (int) 9 ); // version
			writer.Write( m_WalkingRange );

			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.WriteDeltaTime( m_End );

			writer.Write( m_CreaturesName.Count );

			for ( int i = 0; i < m_CreaturesName.Count; ++i )
				writer.Write( m_CreaturesName[i] );

			writer.Write( m_Creatures.Count );

			for ( int i = 0; i < m_Creatures.Count; ++i )
			{
				object o = m_Creatures[i];

				if ( o is Item )
					writer.Write( (Item)o );
				else if ( o is Mobile )
					writer.Write( (Mobile)o );
				else
					writer.Write( Serial.MinusOne );
			}
			
			
			writer.Write((TimeSpan)m_cycledelay);
			writer.Write((bool)m_felandtram);
			writer.Write((bool)m_smartspawn);
			writer.Write((bool)m_IgnoreCanSpawn);
			writer.Write((int)m_TriggerDistance);
			writer.Write((bool)m_ignorehome);
			writer.WriteDeltaTime(m_nextspawndate);
		}

		private static WarnTimer m_WarnTimer;

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

			int version = reader.ReadInt();
			int a = 4; //lol
			switch ( a )
			{
				case 4:
				{
					m_WalkingRange = reader.ReadInt();

					goto case 3;
				}
				case 3:
				case 2:
				{
					m_WayPoint = reader.ReadItem() as WayPoint;

					goto case 1;
				}

				case 1:
				{
					m_Group = reader.ReadBool();
					
					goto case 0;
				}

				case 0:
				{
					m_MinDelay = reader.ReadTimeSpan();
					m_MaxDelay = reader.ReadTimeSpan();
					m_Count = reader.ReadInt();
					m_Team = reader.ReadInt();
					m_HomeRange = reader.ReadInt();
					m_Running = reader.ReadBool();

					TimeSpan ts = TimeSpan.Zero;

					if ( m_Running )
						ts = reader.ReadDeltaTime() - DateTime.Now;
					
					int size = reader.ReadInt();

					m_CreaturesName = new List<string>( size );

					for ( int i = 0; i < size; ++i )
					{
						string typeName = reader.ReadString();

						m_CreaturesName.Add( typeName );

						if ( SpawnerType.GetType( typeName ) == null )
						{
							if ( m_WarnTimer == null )
								m_WarnTimer = new WarnTimer();

							m_WarnTimer.Add( Location, Map, typeName );
						}
					}

					int count = reader.ReadInt();

					m_Creatures = new ArrayList( count );

					for ( int i = 0; i < count; ++i )
					{
						IEntity e = World.FindEntity( reader.ReadInt() );

						if ( e != null )
							m_Creatures.Add( e );
					}

					break;
				}
			}

			if ( version < 3 && Weight == 0 )
				Weight = -1;
				
			if(version>=5)
			{
				m_cycledelay=reader.ReadTimeSpan();
				m_felandtram=reader.ReadBool();
				m_smartspawn=reader.ReadBool();
			}			
			
			if(version >=6)
			{
				m_IgnoreCanSpawn=reader.ReadBool();
			}
			
			if(version>=7)
			{
				m_TriggerDistance=reader.ReadInt();		
			}
			
			if(version>=8)
			{
				m_ignorehome=reader.ReadBool();
			}
			
			if(version>=9)
			{
				m_nextspawndate=reader.ReadDeltaTime();
			}
			


			
			
			//randomize next spawn date
			int minSeconds = (int)m_MinDelay.TotalSeconds;
			int maxSeconds = (int)m_MaxDelay.TotalSeconds;			
			
			if(m_Creatures.Count<ToSpawn)LastSpawnDate=DateTime.Now;	
	
		
			
			if ( m_Running )	DoTimer( TimeSpan.FromSeconds(1+Utility.Random((int)m_cycledelay.TotalSeconds) ));			

			if(FelandTram && this.Map!=Map.Trammel && this.Map!=Map.Felucca)
			{
			FelandTram=false;
			RemoveCreatures();
			}
		}
		
		
		
		//moves the mob to each 4 corners and each 4 midpoints, to quickly get a glance of the spawner's range
		
		public void CornerWalk(Mobile mob)
		{
		new CornerWalkTimer(mob,this).Start();		
		}
		
		
		private class CornerWalkTimer : Timer
		{
		int step=0;
		Mobile m_mob;
		Point3D origin;
		Map originmap;
		Map map;
		int range;
		int x;
		int y;
		
			public CornerWalkTimer(Mobile mob,Spawner spawner) : base(TimeSpan.FromSeconds( 0 ), TimeSpan.FromSeconds( 1.0 ) )
			{
			m_mob=mob;
			x=spawner.X;
			y=spawner.Y;
			map=spawner.Map;
			range=spawner.HomeRange;
			
			origin=mob.Location;
			originmap=mob.Map;
			}		
			

			protected override void OnTick()
			{
				switch(step)
				{
					case 0:
					{
					m_mob.MoveToWorld(new Point3D(x-range,y-range,m_mob.Z),map);
					break;
					}					
					case 1:
					{
					m_mob.MoveToWorld(new Point3D(x,y-range,m_mob.Z),map);
					break;
					}
					case 2:
					{
					m_mob.MoveToWorld(new Point3D(x+range,y-range,m_mob.Z),map);
					break;
					}
					case 3:
					{
					m_mob.MoveToWorld(new Point3D(x+range,y,m_mob.Z),map);
					break;
					}					
					case 4:
					{
					m_mob.MoveToWorld(new Point3D(x+range,y+range,m_mob.Z),map);
					break;
					}	
					case 5:
					{
					m_mob.MoveToWorld(new Point3D(x,y+range,m_mob.Z),map);
					break;
					}		
					case 6:
					{
					m_mob.MoveToWorld(new Point3D(x-range,y+range,m_mob.Z),map);
					break;
					}		
					case 7:
					{
					m_mob.MoveToWorld(new Point3D(x-range,y,m_mob.Z),map);
					break;
					}
					default:
					{
					m_mob.MoveToWorld(origin,originmap);
					Stop();
					break;
					}					
				}
				
			step++;	
			}
		
		}
		

		private class WarnTimer : Timer
		{
			private ArrayList m_List;

			private class WarnEntry
			{
				public Point3D m_Point;
				public Map m_Map;
				public string m_Name;

				public WarnEntry( Point3D p, Map map, string name )
				{
					m_Point = p;
					m_Map = map;
					m_Name = name;
				}
			}

			public WarnTimer() : base( TimeSpan.FromSeconds( 1.0 ) )
			{
				m_List = new ArrayList();
				Start();
			}

			public void Add( Point3D p, Map map, string name )
			{
				m_List.Add( new WarnEntry( 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 ( WarnEntry 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
				{
				}
			}
		}
	}
}