/**********************************************************
RunUO 2.0 AoV C# script file
Official Age of Valor Script :: www.uovalor.com
Last modified by Red Squirrel on Jan-02-2010 02:32:22pm
Filepath: scripts\_custom\engines\Event System\BaseEventController.cs
Lines of code: 904
***********************************************************/


using System;
using Server.Items;
using Server.Gumps;
using System.Collections;
using System.Collections.Generic;
using Server.Network;
using Server.Mobiles;
using Server.Commands;
using Server.Regions;
using Server.Targeting;

namespace Server.EventSystem
{
	public abstract class BaseEventController : Item
	{
		public override bool DisplayWeight { get { return ( false ); } }
		
		
		public static void Initialize()
		{
			if(AllEvents==null)AllEvents=new List<BaseEventController>();
		}
		
		
		//non serialized
		private static List<BaseEventController> AllEvents;
		private static Dictionary<Mobile,BaseEventController> allmembers;
		private static bool GlobalEventStarted=false;
		private static BaseEventController ongoingevent;
		
		//overridables:
		public virtual int m_MinTeams { get { return  1; } }
		public virtual int m_MaxTeams { get { return  1; } }
		public virtual bool RunAutoTeamBalance { get { return true; } }		
		public virtual TimeSpan TickInterval { get { return TimeSpan.FromSeconds(1); } }		
			
		
		//serialized vars:		
		private List<Mobile> RegisteredMobs;  	//all registered mobs
		
		
		private int m_EventStatus=0;
		private int m_MinMembers=0;
		private int m_Teams=0;		
		private int m_MembersPerTeam;				
		private string m_EventName="new event";		
		private string m_EventDesc="new event";	
		private int m_SignupMinutes=35;
		private int m_runrepeats;
		private DateTime m_nextautostart;
		private TimeSpan m_autostartinterval=TimeSpan.FromHours(16);
		private bool m_autostart;
		private Map m_TeleportStartMap;
		private Point3D m_TeleportStartLoc;
		private Map m_TeleportStopMap=Map.Felucca;
		private Point3D m_TeleportStopLoc= new Point3D(5200,1202,7);
		private bool m_allowmount;		
		private int m_RanTimes;
		
		//serialize internal vars
		private DateTime starttime;
	
		
		//get/set functions:	
		[CommandProperty( AccessLevel.GameMaster )]
		public int EventStatus //0=inactive, 1=preregister, 2=on
		{
			get { return m_EventStatus; }
		}
				
		
		[CommandProperty( AccessLevel.GameMaster )]
		public int MinMembers
		{
			get { return TeamsAmt<m_MinMembers ? m_MinMembers : TeamsAmt; }
			set { m_MinMembers = value;Modify(); }
		}
		
		[CommandProperty( AccessLevel.GameMaster )]
		public int TeamsAmt
		{
			get { return m_Teams; }
			set { 
					m_Teams = value;
					if(m_Teams>m_MaxTeams)m_Teams=m_MaxTeams;
					else if(m_Teams<m_MinTeams)m_Teams=m_MinTeams;
					Modify();
				}
		}	
		
		[CommandProperty( AccessLevel.GameMaster )]
		public int MembersPerTeam
		{
			get { return m_MembersPerTeam; }
			set { 
					if(m_MinTeams==0 && m_MaxTeams==0)
					{					
					m_MembersPerTeam = value;
					m_Teams=0;
					Modify();
					}
				}
		}		
		
		[CommandProperty( AccessLevel.GameMaster )]
		public string EventName
		{
			get { return m_EventName; }
			set { m_EventName = value; Modify();}
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public string EventDesc
		{
			get { return m_EventDesc; }
			set { m_EventDesc = value;Modify(); }
		}		
		
		[CommandProperty( AccessLevel.GameMaster )]
		public int SignupMinutes
		{
			get { return m_SignupMinutes; }
			set { m_SignupMinutes = value; Modify();}
		}	

		[CommandProperty( AccessLevel.GameMaster )]
		public int SignupMinutesLeft
		{
			get { 
			TimeSpan left = starttime.Subtract ( DateTime.Now );
			
			return (int)left.TotalMinutes; 			
			}
		}		
			
		[CommandProperty( AccessLevel.GameMaster )]
		public int RunRepeats
		{
			get { return m_runrepeats; }
			set { m_runrepeats = value;Modify(); }
		}	
	
		[CommandProperty( AccessLevel.GameMaster )]
		public DateTime NextAutoStart
		{
			get { return m_nextautostart; }
			set { m_nextautostart = value;Modify(); }
		}	

		[CommandProperty( AccessLevel.GameMaster )]
		public TimeSpan AutoStartInterval
		{
			get { return m_autostartinterval; }
			set { m_autostartinterval = value;Modify(); }
		}			
	
		[CommandProperty( AccessLevel.GameMaster )]
		public bool Autostart
		{
			get { return m_autostart; }
			set { 
			if(m_autostart!=value)
			{
				m_autostart = value; 			
				if(value==true)
				{
					NextAutoStart=DateTime.Now+TimeSpan.FromSeconds(Utility.Random((int)AutoStartInterval.TotalSeconds));
					AddToGlobalEventList(true);
				}
				else RemoveFromGlobalEventList(true);
			}
			
						
			}
		}	
		
	
		[CommandProperty( AccessLevel.GameMaster )]
		public Map TeleportStartMap
		{
			get { return m_TeleportStartMap; }
			set { m_TeleportStartMap = value;Modify(); }
		}	
		
		[CommandProperty( AccessLevel.GameMaster )]
		public Point3D TeleportStartLoc
		{
			get { return m_TeleportStartLoc; }
			set { m_TeleportStartLoc = value;Modify(); }
		}	
		
		[CommandProperty( AccessLevel.GameMaster )]
		public Map TeleportStopMap
		{
			get { return m_TeleportStopMap; }
			set { m_TeleportStopMap = value;Modify(); }
		}	

		[CommandProperty( AccessLevel.GameMaster )]
		public Point3D TeleportStopLoc
		{
			get { return m_TeleportStopLoc; }
			set { m_TeleportStopLoc = value;Modify(); }
		}		

		[CommandProperty( AccessLevel.GameMaster )]
		public bool AllowMount
		{
			get { return m_allowmount; }
			set { m_allowmount = value;Modify(); }
		}	
		
		[CommandProperty( AccessLevel.GameMaster )]
		public int RanTimes
		{
			get { return m_RanTimes; }
			set { m_RanTimes = value;Modify(); }
		}	
		
		
		
		
		
		
		
		public override string DefaultName
		{
			get { return "Event Controller"; }
		}
		
		//this used for creation and load only
		private void InitArrays()
		{
			if(RegisteredMobs==null)RegisteredMobs = new List<Mobile>();
			if(allmembers==null)allmembers= new Dictionary<Mobile,BaseEventController>();
		}
		

		
		public BaseEventController() : base( 0xEDC )
		{
			Movable = false;
			Visible = false;

			m_nextautostart=DateTime.Now+TimeSpan.FromHours(1);
			
			InitArrays();
		}
		
		
		
		//this function removes deleted/null objects from event arrays.  This is cpu intense so should only be called when really needed.
		private void ArrayDefrag()
		{
			Debugger.Write("event","{0} ArrayDefrag()",this);
			
			//RegisteredMobs
			//==========================================================================
			List <Mobile> mobstodel = new List<Mobile>();
			
			foreach(Mobile mob in RegisteredMobs)
			{
				if(mob==null || mob.Deleted)mobstodel.Add(mob);			
			}
			
			foreach(Mobile mob in mobstodel)
			{
				RegisteredMobs.Remove(mob);
			}
			mobstodel.Clear();
			
		}
		
		
		public List<Mobile> GetRegisteredMobs()
		{
			return RegisteredMobs;
		}
		
		
		public override void OnDoubleClick(Mobile m)
		{
			if(m.InRange(this,5) && m.InLOS(this))m.SendGump(new RegisteredGump(this,m,0));	
		}
		
		
		public override void Delete()
		{
			if(Autostart || EventStatus!=0)return; //disable deleting events that are in autostart mode or started
			
			RemoveFromGlobalEventList(true);
			
			base.Delete();
		}
		
		public void ConsoleDebug(string data)
		{
			Debugger.Write("event","AutoEvent[{0}] {1}",EventName,data);
		}		
		
		public void EventBroadcast(string message,bool eventmembersonly)
		{
			Debugger.Write("eventbroadcast","{0} broadcast: {1} {2}",this,message,eventmembersonly); //debug
			
			foreach ( NetState state in NetState.Instances )
			{
				Mobile m = state.Mobile;
				if ( m == null )continue;
				
				
				
				if(eventmembersonly && !RegisteredMobs.Contains(m))continue;

				
					m.SendMessage( 0x35, message );
			}
		}
	
		
		public bool CanJoin(Mobile mob,bool showmsg)
		{
			if(mob.AccessLevel>AccessLevel.Player)return false;
			
			string error="";
			
			if(EventStatus!=1)error="Registration is closed for this event";
			else if(allmembers!=null && allmembers.ContainsKey(mob))error="You are already joined to an event";
			else if(mob is PlayerMobile && ((PlayerMobile)mob).DesignContext!=null)error="You cannot join an event while in house customization mode";
			
			if(error!="")
			{
				if(showmsg)mob.SendMessage(error);
				return false;
			}
			
			return true;
		}

		
		//sets global event info
		private static void SetCurEvent(BaseEventController theevent)
		{
			GlobalEventStarted=true;
			ongoingevent=theevent;
		}
		
		//unsets global event info
		private static void UnSetCurEvent()
		{
			GlobalEventStarted=false;
					
			if(ongoingevent==null)return;
			
			//delete registered
			
			foreach(Mobile mob in ongoingevent.RegisteredMobs)
			{
				allmembers.Remove(mob);
				mob.SolidHueOverride=-1;
				mob.Team=0;
			}
				
			
			ongoingevent.RegisteredMobs.Clear();	
			
			ongoingevent=null;
		}
		
		public static BaseEventController GetCurEvent()
		{
			return ongoingevent;
		}
		
		public static bool IsInAnEvent(Mobile m)
		{
			if(allmembers==null)return false;
			return allmembers.ContainsKey(m);
		}
		
		
		public void PostPone(int min,int max)
		{
			if(min<1)min=1;
			if(max<min)max=min;
			
			Modify();
			
			int minutes=min+Utility.Random(max-min+1);
			
			ConsoleDebug(String.Format("Postponed for {0} minutes",minutes));
			
			NextAutoStart=DateTime.Now+TimeSpan.FromMinutes(minutes);
		}
		
		
		
		//this function started to give users a chance to register for the event	
		public void PreStartEvent()
		{
			if(GlobalEventStarted)return;
			
			Modify();
			
			//check if there is enough mobiles to start this event, if yes send them a register message
			
			m_EventStatus=1;
			SetCurEvent(this);
			
			List<Mobile> list = new List<Mobile>();

			List<NetState> states = NetState.Instances;

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

				if ( mob == null ) continue;
					
				if(!CanJoin(mob,false))continue;

				list.Add( mob );
			}
			
			if(list.Count<MinMembers)
			{
				//not enough people to start event, pospone it				
				ConsoleDebug("Attempted to start. Not enough users. ");
				
				m_EventStatus=0;
				RanTimes=0;
				UnSetCurEvent();
				PostPone(1,1+(int)AutoStartInterval.TotalMinutes);			
				return;
			}
			
			starttime=DateTime.Now+TimeSpan.FromMinutes(RanTimes>0 ? 5 : SignupMinutes);
			
			for(int i=0;i<list.Count;i++)
			{
				if(list[i] is PlayerMobile)
				{
				PlayerMobile pm = list[i] as PlayerMobile;
				pm.SendGump(new eventsystemgump(this));
				}
			}
			
			ConsoleDebug("Registration opened");
			
			new EventTimer(this).Start();			
			OnPreStart();
		}
		
		protected virtual void OnPreStart()
		{
		}

		
		//this function starts the actual event		
		protected void StartEvent()
		{
			//validate if we can start now or not
			Modify();
			
			if(!ValidateRegisteredMobs())
			{
				EventBroadcast(String.Format("Not enough players have registered for event \"{0}\".  It has been cancelled.",EventName ),false);
				m_EventStatus=0;
				UnSetCurEvent();
				ConsoleDebug("Not enough people registered, cancelling");
				RanTimes=RunRepeats;
				RanTimes=0;
				NextAutoStart=DateTime.Now+AutoStartInterval;
				return;
			}			
			m_EventStatus=2;
			
			


			
			if(RunAutoTeamBalance)AutoTeamBalance();			
			
			ConsoleDebug("Event started");
			EventBroadcast(String.Format("The event \"{0}\" has started!",EventName),true );	
						
			TeleportStart();
			OnStartEvent();
		}
		
		protected virtual void OnStartEvent()
		{
		}
	
		
		//this stops the event and empties the arrays - called by child class as it takes over once StartEvent is called.
		public void StopEvent()
		{
			Modify();
			ConsoleDebug("Event ended");
			EventBroadcast(String.Format("The event \"{0}\" has ended.",EventName),true );	
			
			if(RanTimes>=RunRepeats)
			{
				NextAutoStart=DateTime.Now+AutoStartInterval;
				RanTimes=0;
			}
			else
			{
				NextAutoStart=DateTime.Now+TimeSpan.FromMinutes(1);
				RanTimes++;
			}
			
			
			if(m_EventStatus==2)TeleportStop();
			UnSetCurEvent();
			OnStopEvent();	

			m_EventStatus=0;
			lastwarning= new DateTime();
		}
		
		protected virtual void OnStopEvent()
		{
		}
		
		
		//moves a mobile properly (if DCed, does not bring back to world)
		public void MoveMobile(Mobile m,Point3D locp3d, Map locmap)
		{
			m.Hidden=false;
			if(m.NetState!=null || m is BaseCreature)
			{			
				m.MoveToWorld(locp3d,locmap);
			}
			else
			{
				m.Map=Map.Internal;
				m.LogoutLocation=locp3d;
				m.LogoutMap=locmap;		
			}
		}
		
		
		//teleports users when event starts 
		public void TeleportStart()
		{
			foreach(Mobile mob in RegisteredMobs)
			{
				StartTeleportMobile(mob);
				if(!AllowMount)mob.Dismount();
				if(mob is PlayerMobile)((PlayerMobile)mob).StableAllPets(true);
			}
		}
		
		//override this to use different location per team
		public virtual void StartTeleportMobile(Mobile mob)
		{
			if(TeleportStartLoc==Point3D.Zero)return;
			MoveMobile(mob,TeleportStartLoc,TeleportStartMap);
		}
		
		
		//teleports users when event stops
		public void TeleportStop()
		{
			foreach(Mobile mob in RegisteredMobs)
			{
				StopTeleportMobile(mob);
				mob.Warmode=false;
			}
		}		
		
		//override this to use different location per team
		public virtual void StopTeleportMobile(Mobile mob)
		{
			if(TeleportStopLoc==Point3D.Zero)return;
			MoveMobile(mob,TeleportStopLoc,TeleportStopMap);
		}		
		
		
		//unregisters mobile
		public void UnRegisterMobile(Mobile mob)
		{
			if(RegisteredMobs.Contains(mob))RegisteredMobs.Remove(mob);
			if(allmembers.ContainsKey(mob))allmembers.Remove(mob);
			mob.SolidHueOverride=-1;
			
			if(mob.Region!=null && mob.Region is CustomRegion && ((CustomRegion)mob.Region).CurEventControlStone==this)
			{
				StopTeleportMobile(mob);
			}
		}
		
		
		
		//this registers a player
		public bool RegisterMobile(Mobile mob)
		{
			if(!CanJoin(mob,true))return false;	
			
			if(!RegisteredMobs.Contains(mob))RegisteredMobs.Add(mob);
			if(!allmembers.ContainsKey(mob))allmembers.Add(mob,this);

			OnRegister(mob);
			
			return true;		
		}
		
	
		protected virtual void OnRegister(Mobile mob)
		{
		}
		
		
		//this is used to check if anyone went offline or something, so we remove them from array - also to check if enough people actually registered
		public bool ValidateRegisteredMobs()
		{
			ArrayDefrag();			
			
			List<Mobile> todel = new List<Mobile>();
			
			foreach(Mobile mob in RegisteredMobs)
			{
				if(mob==null || (mob.NetState==null && mob is PlayerMobile) || mob.Deleted)todel.Add(mob);
			}

			foreach(Mobile mob in todel)
			{
				RegisteredMobs.Remove(mob);
			}
			
			todel.Clear();

			return RegisteredMobs.Count>=MinMembers;
		}		
		
		

		
		
		
		
		//ticks durring actual event
		protected virtual void OnEventTick()
		{
		}
		
		DateTime lastwarning; //used to warn that the event will start
		
		private int GetNextWarningDelay()
		{
			if(starttime<(DateTime.Now+TimeSpan.FromMinutes(1)))return 1;
			if(starttime<(DateTime.Now+TimeSpan.FromMinutes(10)))return 5;
			if(starttime<(DateTime.Now+TimeSpan.FromMinutes(30)))return 10;
			if(starttime<(DateTime.Now+TimeSpan.FromHours(1)))return 50;
			else return 180;
		}
		
		//ticks during registration  -- not to be overrided
		protected void OnRegistrationTick()
		{
			if(DateTime.Now>starttime)StartEvent();
			else if(DateTime.Now>(lastwarning+TimeSpan.FromMinutes(GetNextWarningDelay())))
			{
				EventBroadcast(String.Format("Registration has opened for \"{0}\".  You have {1} minutes to register.  Type [event to register if you have not already.  You will be teleported once the event starts.",EventName,SignupMinutesLeft),false );	
				lastwarning=DateTime.Now;
			}
		
		}
		
		
		//region stuff
		//NOTE: this requires that a custom region is setup and assigned to this event controller!
		
		public virtual bool RegionOnDamage(Mobile m, ref int damage)
		{
			return true;
		}
		
		public virtual bool RegionOnResurrect(Mobile m)
		{
			return false;
		}		
		
		public virtual bool RegionOnBeforeDeath(Mobile m)
		{
			return true;
		}			
		
		public virtual bool RegionAllowBeneficial(Mobile from, Mobile target)
		{
			if(target is PlayerMobile && target.Team==0)return false;
			return (from.Team==target.Team && from.Team!=0) || (from is BaseCreature && from.Team==0);
		}
		
		public virtual bool RegionAllowHarmful(Mobile from, Mobile target)
		{
			if(target is PlayerMobile && target.Team==0)return false;
			return (from.Team!=target.Team && from.Team!=0) || (from is BaseCreature && from.Team==0);
		}		
		
		public virtual bool RegionOnDoubleClick(Mobile m, object o)
		{
			if(m.Team==0 && m.AccessLevel==AccessLevel.Player && !(o is gmhat) && !(o is BaseDoor))
			{
				m.SendMessage("You are not part of this event and cannot do that!");
				return false;
			}
			
			return true;
		}
		
		public virtual bool RegionOnSkillUse(Mobile m, int skill)
		{
			if(m.Team==0 && m.AccessLevel==AccessLevel.Player)
			{
				m.SendMessage("You are not part of this event and cannot do that!");
				return false;
			}
			
			return true;
		}	

		public virtual bool RegionOnBeginSpellCast(Mobile m, ISpell s)
		{
			if(m.Team==0 && m.AccessLevel==AccessLevel.Player)
			{
				m.SendMessage("You are not part of this event and cannot do that!");
				return false;
			}
			
			return true;
		}		
		
		//end region stuff
		
		
		//can override this based on event type like CTF
		public virtual int GetTeamHueOverride(int teamnum)
		{
			return -1;
		}
		
		public virtual string GetTeamName(int teamnum)
		{
			return String.Format("Team {0}",teamnum);
		}
		
		
		
		//this automaticly sets up teams in a balanced matter
		public void AutoTeamBalance()
		{
			int Tteamsamt=TeamsAmt;
			int Tmembersperteam=MembersPerTeam;
			if(Tmembersperteam<1)Tmembersperteam=1;

			
			if(Tteamsamt==0) //teams not specified, only players per team, so we make up as many teams needed
			{
				int players=RegisteredMobs.Count;
				
				Tteamsamt=(int)Math.Floor((double)(players/Tmembersperteam));
			}
			
			
			//now that we know how many teams we have, disperse members to each team
							
			int tmpcounter=0;
			foreach(Mobile mob in RegisteredMobs)
			{		
				mob.Team=tmpcounter+1;
				mob.SolidHueOverride=GetTeamHueOverride(tmpcounter+1);
				
				tmpcounter++;
				if(tmpcounter>=Tteamsamt)tmpcounter=0;
			}
			
		}
		
		
		
		//this is called periodicly by the timer to see if any event should be started
		public static void CheckEventsStart()
		{
			BaseEventController tostart=null;
			
			Debugger.Write("event","Checking if an event should start...");
			
			foreach(BaseEventController Bec in BaseEventController.AllEvents)
			{
				Debugger.Write("event","Checking: {0} {1}",Bec,Bec.EventName);
				
				if(Bec.Deleted || Bec==null)
				{
					Debugger.Write("event","{0} Deleted, skippng",Bec);
					continue;
				}
				
				if(Bec.NextAutoStart<=DateTime.Now)
				{
					//If there is already an event, just offset the time
					if(GlobalEventStarted && Bec!=ongoingevent)
					{
						Debugger.Write("event","{0} event already started, postponing...",Bec);
						Bec.PostPone(30,60); 
					}
					else
					{
						Debugger.Write("event","{0} set to start",Bec);
						tostart=Bec;
					}
				}
				
				//if there is an event that has runrepeats left, dont try to start another
				if(Bec.RanTimes!=0 && Bec.Autostart)
				{
					Debugger.Write("event","{0} rantimes not 0, this one will have to start next",Bec);
					if(tostart!=Bec)tostart=null;
					break;
				}
			}
			
			if(tostart!=null)tostart.PreStartEvent();
		}
		
		
		
		public void AddToGlobalEventList(bool showdebug)
		{
			//add to global array of events
			if(AllEvents==null)AllEvents=new List<BaseEventController>();
			AllEvents.Add(this);
			if(showdebug)ConsoleDebug("Added to auto event pool");
		}
		
		public void RemoveFromGlobalEventList(bool showdebug)
		{
			if(AllEvents==null)AllEvents=new List<BaseEventController>();
			AllEvents.Remove(this);
			if(showdebug)ConsoleDebug("Removed from auto event pool");
		}
		
		
		
			public class EventTimer : Timer
			{				
				BaseEventController owner;
				
				public EventTimer(BaseEventController powner) : base( TimeSpan.FromSeconds( 0 ), powner.TickInterval )
				{
					Priority = ComputePriority( powner.TickInterval );
					owner=powner;
				}		
				
				protected override void OnTick()
				{
					Debugger.Write("event","{0} EventTimer tick status (1)",owner,owner.EventStatus);
					
					if(owner.EventStatus==1)owner.OnRegistrationTick();
					else if(owner.EventStatus==2)owner.OnEventTick();
					else Stop();
					
					owner.Modify();
				}		
			}

			
			public class DeathTeleportTimer : Timer
			{
				BaseEventController owner;
				Mobile totele;
				Point3D location;
				Map map;
				bool regenstats;
				
				public DeathTeleportTimer(BaseEventController powner,Mobile ptotele,Point3D plocation,Map pmap,int teledelay,bool pregenstats) : base(TimeSpan.FromSeconds( teledelay ) )
				{
					Priority = TimerPriority.OneSecond;
					owner=powner;
					totele=ptotele;
					location=plocation;
					map=pmap;
					regenstats=pregenstats;
					totele.Frozen=true;
					
					if(teledelay>1)totele.SendMessage("You will respawn in {0} seconds.",teledelay);
				}		
				
				public DeathTeleportTimer(BaseEventController powner,Mobile ptotele,Point3D plocation,Map pmap,int teledelay) : this (powner,ptotele,plocation,pmap,teledelay,false)
				{
				}
				
				protected override void OnTick()
				{					
					totele.Frozen=false;
					totele.IsFakeDead=false;
					if(owner!=null && owner.EventStatus==2)
					{
						Debugger.Write("event","{0} DeathTeleportTimer.OnTick() for {1}",owner,totele);						
						owner.MoveMobile(totele,location,map);
						totele.Resurrect();
						Spells.Chivalry.RemoveCurseSpell.RemoveCurses(totele);
						totele.Poison=null;
						
						if(regenstats)
						{
							Debugger.Write("event","{0} DeathTeleportTimer.OnTick() replenishing stats for {1}",owner,totele);
							totele.HitsPerc=100;
							totele.ManaPerc=100;
							totele.StamPerc=100;
						}						
					}
					else Stop();
				}		
			}			
		
		
		
		public BaseEventController( Serial serial ) : base( serial )
		{
		}

		public override void Serialize( GenericWriter writer )
		{
			base.Serialize( writer );
			writer.Write( (int) 1 ); // version
			
			ArrayDefrag();
			
			writer.Write(m_EventStatus);
			writer.Write(m_MinMembers);
			writer.Write(m_Teams);
			writer.Write(m_EventName);
			writer.Write(m_EventDesc);
			writer.Write(m_SignupMinutes);
			writer.Write(m_runrepeats);
			writer.WriteDeltaTime(m_nextautostart);
			writer.Write(m_autostartinterval);
			writer.Write(starttime);
			writer.Write(RanTimes);
			writer.Write(m_autostart);		
			writer.Write(m_TeleportStartMap);
			writer.Write(m_TeleportStartLoc);
			writer.Write(m_TeleportStopMap);
			writer.Write(m_TeleportStopLoc);	
			writer.Write(m_allowmount);
			writer.Write(m_MembersPerTeam);
			
		
			//registered mobs
			writer.Write(RegisteredMobs.Count);
			foreach(Mobile mob in RegisteredMobs)
			{
				writer.Write(mob);				
			}	
			
		}
		
		public override void Deserialize( GenericReader reader )
		{
			base.Deserialize( reader );
			int version = reader.ReadInt();
			InitArrays();
			
			m_EventStatus=reader.ReadInt();
			m_MinMembers=reader.ReadInt();
			m_Teams=reader.ReadInt();
			m_EventName=reader.ReadString();
			m_EventDesc=reader.ReadString();
			m_SignupMinutes=reader.ReadInt();
			m_runrepeats=reader.ReadInt();
			m_nextautostart=(reader.ReadDeltaTime() + TimeSpan.FromMinutes(10));
			m_autostartinterval=reader.ReadTimeSpan();		
			starttime=reader.ReadDateTime();
			RanTimes=reader.ReadInt();
			m_autostart=reader.ReadBool();			
			TeleportStartMap=reader.ReadMap();
			TeleportStartLoc=reader.ReadPoint3D();
			TeleportStopMap=reader.ReadMap();
			TeleportStopLoc=reader.ReadPoint3D();	
			m_allowmount=reader.ReadBool();
			
			if(version>=1)
			{
			m_MembersPerTeam=reader.ReadInt();
			}
			
			
			//registered mobs
			int members = reader.ReadInt();
			
			for(int i=0;i<members;i++)
			{
				Mobile addmob=reader.ReadMobile();
				
				RegisteredMobs.Add(addmob);
				allmembers.Add(addmob,this);
				
				if(EventStatus==2)addmob.SolidHueOverride=GetTeamHueOverride(addmob.Team);
				
			}
			
			//stuff to do after deserialize:
			
			if(EventStatus==1)starttime = DateTime.Now + TimeSpan.FromMinutes(Math.Max(10,SignupMinutes));
			
			if(m_EventStatus>0)
			{
				SetCurEvent(this);
				new EventTimer(this).Start();
			}
			
			
			
			if(m_autostart)AddToGlobalEventList(false);
		}
	}

	
	//this timer runs on a per minute basis and checks if any registered events should be started
	public class AutoEventTimer : Timer
	{
		public static void Initialize()
		{
			new AutoEventTimer().Start();
		}
		
		
		public AutoEventTimer() : base( TimeSpan.FromMinutes( 1.0 ), TimeSpan.FromMinutes( 1.0 ) )
		{
			Priority = TimerPriority.OneMinute;
		}		
		
		protected override void OnTick()
		{
			BaseEventController.CheckEventsStart();
		}		
	}	


	public class EventCommand
	{
		public static void Initialize()
		{
			CommandSystem.Register( "event", AccessLevel.Player, new CommandEventHandler( EventCommand_OnCommand ) );
		}

		[Usage( "event" )]
		[Description( "pops up gump to register at an event" )]
		private static void EventCommand_OnCommand( CommandEventArgs e )
		{		
			PlayerMobile pm = e.Mobile as PlayerMobile;
			
			BaseEventController theevent=BaseEventController.GetCurEvent();
		
			if(theevent!=null)
			{
				if(pm.AccessLevel>=AccessLevel.Counselor || BaseEventController.IsInAnEvent(pm))
				{
					pm.CloseGump(typeof(RegisteredGump));
					pm.SendGump(new RegisteredGump(theevent,pm,0));				
				}
				else if(theevent.CanJoin(pm,true))
				{			
					pm.CloseGump(typeof(eventsystemgump));
					pm.SendGump(new eventsystemgump(theevent));		
				}
			}
			else pm.SendMessage("There is no ongoing event at the moment");
		}
	}
	



	public class eventsystemgump : Gump
	{
	BaseEventController m_stone;
		public eventsystemgump(BaseEventController stone): base( 200, 200 )
		{
			m_stone=stone;
			this.Closable=true;
			this.Disposable=true;
			this.Dragable=true;
			this.Resizable=false;
			this.AddPage(0);
			this.AddBackground(0, 0, 407, 401, 83);
			this.AddLabel(28, 12, 1149, @stone.EventName);
			this.AddButton(177, 350, 1154, 1155, (int)Buttons.Button1, GumpButtonType.Reply, 0);
			this.AddLabel(208, 350, 1149, @"Sign up now!");
			this.AddImage(326, 4, 11567);
			
			this.AddHtml( 25, 42, 355, 310, @String.Format("<font color=#FFFFFF>An auto-ran public event will start in {0} minutes.  If you wish to participate, click ok.  You will be teleported when the event starts.<br><br><i>{1}</i></font>",stone.SignupMinutesLeft,stone.EventDesc), (bool)false, (bool)true);
			
			
		}
		
		public enum Buttons
		{
			Exit,
			Button1,
		}
		
		public override void OnResponse( NetState state, RelayInfo info )
		{
			Mobile from = state.Mobile;

			if ( info.ButtonID == 1 )
			{
			if(m_stone.RegisterMobile(from)) from.SendMessage("You have been registered for the event.");
			}
		}	
	}
	
	
	public class RegisteredGump : Gump
	{
		BaseEventController m_stone;
		int m_page;
		
		
		public RegisteredGump(BaseEventController stone,Mobile from, int page): base( 200, 200 )
		{
			m_stone=stone;
			if(stone==null || stone.Deleted)return;
			
			if(page<0)page=0;
			
			m_page=page;
			
			this.Closable=true;
			this.Disposable=true;
			this.Dragable=true;
			this.Resizable=false;
			this.AddPage(0);
			this.AddBackground(0, 0, 407, 411, 83);
			this.AddLabel(28, 12, 1149, String.Format("{0} - Registered Members (page {1})",stone.EventName,page+1));

			const int ipp=15;		//items per page
			const int spacer=15;	//spacer between items
			
			List<Mobile> mobs = stone.GetRegisteredMobs();

			this.AddLabel(30, 40, 1149, "Name");
			this.AddLabel(330, 40, 1149, "Team #");
			
			
			for(int i=page*ipp;(i<mobs.Count && i<(((page+1)*ipp)));i++)
			{
				this.AddLabel(30, 80+ ((i-page*ipp)*spacer), 1149, mobs[i].Name);
				this.AddLabel(360, 80+ ((i-page*ipp)*spacer), 1149, mobs[i].Team.ToString());
			}
			
			
			if(page>0)
			{			
				AddButton( 30, 330, 2466, 2467, 1, GumpButtonType.Reply, 0 );//previous
			}
			
			if(page<(mobs.Count/ipp))
			{
				AddButton( 110, 330, 2469, 2470, 2, GumpButtonType.Reply, 0 );//next
			}
			
			AddLabel(34, 360, 1149,"Unregister from event");
			AddButton( 10, 363,  5601, 5605, 3, GumpButtonType.Reply, 0 );//quit event
			
			if(from.AccessLevel>=AccessLevel.Counselor)
			{			
				AddLabel(34, 375, 1149,"Stop event");
				AddButton( 10, 378,  5601, 5605, 4, GumpButtonType.Reply, 0 );//Stop event

				AddLabel(34+120, 375, 1149,"Register a mob");
				AddButton( 10+120, 378,  5601, 5605, 5, GumpButtonType.Reply, 0 );//Register a mob		
			}
			
		}
		
		
		public override void OnResponse( NetState state, RelayInfo info )
		{
			Mobile from = state.Mobile;

			if ( info.ButtonID == 1 )
			{
				from.SendGump(new RegisteredGump(m_stone,from,m_page-1));
			}
			else if( info.ButtonID == 2 )
			{
				from.SendGump(new RegisteredGump(m_stone,from,m_page+1));
			}
			else if(info.ButtonID==3)
			{
				if(m_stone.EventStatus==1)
				{
					m_stone.UnRegisterMobile(from);		
					from.SendMessage("You have been unregistered from the event");
				}
				else if(m_stone.EventStatus==2)from.SendMessage("The event has already started.");
			}
			else if(info.ButtonID==4 && from.AccessLevel>=AccessLevel.Counselor)
			{
				m_stone.EventBroadcast("The event has been stopped manually",true);			
				m_stone.StopEvent();
			}	
			else if(info.ButtonID==5 && from.AccessLevel>=AccessLevel.Counselor)
			{
				from.Target=new InternalTarget(m_stone);
			}				
			
		}	
		
		private class InternalTarget : Target
		{
			BaseEventController controller;

			public InternalTarget( BaseEventController pcontroller ) : base( 25, false, TargetFlags.None )
			{
				controller=pcontroller;
			}

			protected override void OnTarget( Mobile from, object targeted )
			{
				if(!(targeted is Mobile) || from.AccessLevel<AccessLevel.Counselor)return;
				
				Mobile mob = targeted as Mobile;
				
				if(mob==null)return;
				
				if(controller.CanJoin(mob,false))
				{
					from.SendMessage("{0} has been joined to the event",mob);
					controller.RegisterMobile(mob);
				}
				else from.SendMessage("{0} is already joined.",mob);
				
				from.Target = new InternalTarget(controller);

			}
					
		}
		
	}	
	

		
}








