/**********************************************************
RunUO 2.0 AoV C# script file
Official Age of Valor Script :: www.uovalor.com
Last modified by 
Filepath: scripts\_custom\engines\ML Peerless + misc areas\zzz-items\basepeerlesskey.cs
Lines of code: 1044
***********************************************************/


using System;
using System.Net;
using Server;
using Server.Accounting;
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
using Server.Network;
using Server.Targeting;
using Server.Prompts;
using Server.Regions;
using System.Collections;
using System.Collections.Generic;
using Server.Engines.PartySystem;
using System.Text;
using Server.Spells;

namespace Server.Items
{

	//instead of giving a key, we give a bag, this leaves us better options suchas # of spares to give and such
	public class peerlesskeybag : Bag
	{
		/* This takes a base key and puts it in the bag with matching keys either as spares or others to bring in a party */
		
		public peerlesskeybag(basepeerlesskey basekey, int mainkeys,int seckeys,int seckeysreuse)
		{
		Hue=1150;
		Name="Peerless entry kit";
		DropItem(basekey);
		LootType=LootType.Blessed;
		
			for(int i=0;i<mainkeys;i++)
			{
				basepeerlesskey key = new basepeerlesskey(basekey,true);
				key.isreusable=false;
				key.ItemID=basekey.ItemID;
				key.Hue=basekey.Hue;
				
				DropItem(key);
			}
		
			for(int i=0;i<seckeys;i++)
			{
				basepeerlesskey key = new basepeerlesskey(basekey,false);
				key.isreusable=false;
				key.ItemID=basekey.ItemID;
				key.Hue=basekey.Hue;			
				
				DropItem(key);
			}
		
			for(int i=0;i<seckeysreuse;i++)
			{
				basepeerlesskey key = new basepeerlesskey(basekey,false);
				key.isreusable=true;
				key.ItemID=basekey.ItemID;
				key.Hue=basekey.Hue;			
				
				DropItem(key);
			}		

			DropItem(new peerlesskeyinstructions());	

				
			MaxItems=0;
		}
		
		public override bool TryDropItem( Mobile from, Item dropped, bool sendFullMessage )
		{
		if(dropped is basepeerlesskey)return true;
		from.SendMessage("This bag is meant to temporarly hold peerless keys and cannot have items put into it");
		return false;
		}
		
		public override bool CheckHold( Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight )
		{
		if(item is basepeerlesskey)return true;
		m.SendMessage("This bag is meant to temporarly hold peerless keys and cannot have items put into it");
		return false;		
		}
		
		public peerlesskeybag( Serial serial ) : base( serial )
		{
		}

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

			writer.WriteEncodedInt( (int)0 ); // version
		}

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

			int version = reader.ReadEncodedInt();
		}
	}
	
	public class peerlesskeyinstructions : RedBook	
	{
		public static readonly BookContent Content = new BookContent
			(
				"Peerless Entry Instructions", "",
				new BookPageInfo
				(
				"To summon the boss and",
				"teleport your party, look",
				"for key labeled \"master\"",
				"If muliple exist, use any",
				"You may also find other",
				"keys not laballed as such.",
				"Those are spares in case",
				"a member needs to"
				),
				new BookPageInfo
				(
				"leave and come back.",
				"",
				"ensure your party",
				"is near you to begin.",
				"",
				"Good luck"				
				)

			);

		public override BookContent DefaultContent{ get{ return Content; } }

		[Constructable]
		public peerlesskeyinstructions() : base( false )
		{
		}

		public override void AddNameProperty( ObjectPropertyList list )
		{
			list.Add( "Peerless Entry Instructions" );
		}

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

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

			writer.WriteEncodedInt( (int)0 ); // version
		}

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

			int version = reader.ReadEncodedInt();
		}		

}
	
	


	public class basepeerlesskey : Item
	{
	
	//to serialize
	private double m_keyid;                          //unique generated key ID
	private DateTime m_createdate;            //date key was created
	private TimeSpan m_decaytime;            //time the key is allowed to be idle
	
	private string m_bossname;   				 //name of the boss
	private string m_bosstype;	 				 //class name of the boss
	private Point3D m_bosslocation; 			 //location to spawn the boss
	private Point3D m_teleportlocation; 		 //location to teleport party
	private Rectangle2D m_peerlessroom;	//room area of peerless boss. 


	private Map m_spawnmap;        			 //map to spawn the boss	
	private TimeSpan m_maxkilltime;  		 //max time to kill boss
	private TimeSpan m_maxloottime; 		 //max time to loot boss
	
	bool m_active;
	private DateTime m_spawndate;   		 //date the boss was spawned	
	private Point3D m_originallocation;  	  //location to go to when kicked	
	private Map m_originalmap;    				//location to go to when kicked	

	private Mobile m_peerlessboss;           //the boss itself
	private bool m_ismainkey;                        //if true, this key handles the summon and timers, if false, it just teleports
	private bool m_isreusable;                          //wether or not the key is reusable
	private Mobile m_lastuser;                      //last person to use it (applys for main keys as it has to go back in their pack after, if its reusable)


	private int m_partycount;                      //# of members
	private List<Mobile> m_party;              //party members
	


	//non serialize vars	
	private InternalTimer m_Timer;
	private static Hashtable m_Table = new Hashtable();  //put players in here to ensure they can't go in 2 peerless boss rooms at once (would cause weird issues)
	public bool oktosummon=false; //this is switched to on before sending the gump, the gump then checks against this when submitting, this is to avoid from manipulating packets to send the "ok" without actually opening the gump
	private PeerlessRegion m_region;
	
	//set/get

		[CommandProperty( AccessLevel.GameMaster )]
		public double keyid
		{
			get
			{
				return m_keyid;
			}
			set
			{
				m_keyid = value;Modify();
			}
		}		
		
		[CommandProperty( AccessLevel.GameMaster )]
		public DateTime createdate
		{
			get
			{
				return m_createdate;
			}
			set
			{
				m_createdate = value;Modify();
			}
		}

		[CommandProperty( AccessLevel.GameMaster )]
		public TimeSpan decaytime
		{
			get
			{
				return m_decaytime;
			}
			set
			{
				m_decaytime = value;Modify();
			}
		}		
			
	
		[CommandProperty( AccessLevel.GameMaster )]
		public string bossname
		{
			get
			{
				return m_bossname;
			}
			set
			{
				m_bossname = value;Modify();
			}
		}		

		[CommandProperty( AccessLevel.GameMaster )]
		public string bosstype
		{
			get
			{
				return m_bosstype;
			}
			set
			{
				m_bosstype= value;Modify();
			}
		}						

		[CommandProperty( AccessLevel.GameMaster )]
		public Point3D bosslocation
		{
			get
			{
				return m_bosslocation;
			}
			set
			{
				m_bosslocation = value;Modify();
			}
		}
		
		[CommandProperty( AccessLevel.GameMaster )]
		public Map spawnmap
		{
			get
			{
				return m_spawnmap;
			}
			set
			{
				m_spawnmap = value;Modify();
			}
		}
	
		[CommandProperty( AccessLevel.GameMaster )]
		public Point3D teleportlocation
		{
			get
			{
				return m_teleportlocation;
			}
			set
			{
				m_teleportlocation = value;Modify();
			}
		}	
		
		[CommandProperty( AccessLevel.GameMaster )]
		public Rectangle2D peerlessroom
		{
			get
			{
				return m_peerlessroom;
			}
			set
			{
				m_peerlessroom = value;Modify();
			}
		}		
	
		[CommandProperty( AccessLevel.GameMaster )]
		public DateTime spawndate
		{
			get
			{
				return m_spawndate;
			}
			set
			{
				m_spawndate = value;Modify();
			}
		}	
	
		[CommandProperty( AccessLevel.GameMaster )]
		public TimeSpan maxkilltime
		{
			get
			{
				return m_maxkilltime;
			}
			set
			{
				m_maxkilltime = value;Modify();
			}
		}			
		
		[CommandProperty( AccessLevel.GameMaster )]
		public TimeSpan maxloottime
		{
			get
			{
				return m_maxloottime;
			}
			set
			{
				m_maxloottime = value;Modify();
			}
		}		

		[CommandProperty( AccessLevel.GameMaster )]
		public Point3D originallocation
		{
			get
			{
				return m_originallocation;
			}
			set
			{
				m_originallocation = value;Modify();
			}
		}		
		
		[CommandProperty( AccessLevel.GameMaster )]
		public Map originalmap
		{
			get
			{
				return m_originalmap;
			}
			set
			{
				m_originalmap= value;Modify();
			}
		}				

		[CommandProperty( AccessLevel.GameMaster )]
		public Mobile peerlessboss
		{
			get
			{
				return m_peerlessboss;
			}
			set
			{
				m_peerlessboss = value;Modify();
			}			
		}	

		[CommandProperty( AccessLevel.GameMaster )]
		public bool ismainkey
		{
			get
			{
				return m_ismainkey;
			}
			set
			{
				m_ismainkey = value;Modify();
			}			
		}			

		[CommandProperty( AccessLevel.GameMaster )]
		public bool isreusable
		{
			get
			{
				return m_isreusable;
			}
			set
			{
				m_isreusable = value;Modify();
			}			
		}			
		
		[CommandProperty( AccessLevel.GameMaster )]
		public Mobile lastuser
		{
			get
			{
				return m_lastuser;
			}
			set
			{
				m_lastuser = value;Modify();
			}			
		}				
		
		
		[CommandProperty( AccessLevel.GameMaster )]
		public bool active
		{
			get
			{
				return m_active;
			}
		}		
			

		[CommandProperty( AccessLevel.GameMaster )]
		public int partycount
		{
			get
			{
				return m_partycount;
			}
		}			
		
		
		public override string DefaultName
		{
			get {
			StringBuilder sb = new StringBuilder();
			
			sb.AppendFormat("{0} key. ",bossname);
			if(ismainkey)sb.AppendFormat(" (master)");
			if(isreusable)sb.AppendFormat(" [Reusable]");	
			
			 return sb.ToString(); 
			 }
		}		
		
		
        public override void GetProperties( ObjectPropertyList list )
        {
            base.GetProperties( list );
			
			if(!active)
			{
			
			TimeSpan TLifeSpan = (createdate+decaytime)-DateTime.Now;
			
			int LifeSpan=(int)TLifeSpan.TotalSeconds;			
			
            if(LifeSpan>=0)list.Add( 1072517, LifeSpan.ToString() ); // Lifespan: ~1_val~ seconds
			else list.Add(1060658,"Lifespan \t{0}","Expired!");
			}
        }			


		[Constructable]
		public basepeerlesskey( basepeerlesskey pkey,bool ismainkey) : base( 4110 )
		{
		//this function used to create a sub key linked by ID - if its a main key, it will teleport a whole party, otherwise only teleport the user

			Hue=1150;
			m_active=false;		
			
			//duplicate the key - most important here is probably teleport location
			m_keyid=pkey.keyid;		
			m_bossname=pkey.bossname;
			m_bosstype=pkey.bosstype;
			m_bosslocation=pkey.bosslocation;
			m_teleportlocation=pkey.teleportlocation;
			m_peerlessroom=pkey.peerlessroom;
			m_spawnmap=pkey.spawnmap;
			m_maxkilltime=pkey.maxkilltime;
			m_maxloottime=pkey.maxloottime;

			m_ismainkey=ismainkey;


		    LootType = LootType.Blessed;
			
			m_party = new List<Mobile>();	//this wont be used, but best to create it to be safe
			m_createdate=DateTime.Now;
			if(ismainkey)m_decaytime=TimeSpan.FromMinutes(10);	
			else m_decaytime=m_maxkilltime+m_maxkilltime;	
			
			new ExpireTimer(this).Start();
		}
		

		[Constructable]
		public basepeerlesskey( string bossname,string bosstype) : base( 4110 )
		{
			GenID();//generate a unique (sorta) ID for this key		
			
			Hue=1150;
			
			m_bossname=bossname;
			m_bosstype=bosstype;
			m_active=false;
			
			LootType = LootType.Blessed;
			
			m_party = new List<Mobile>();	
			m_createdate=DateTime.Now;
			if(ismainkey)m_decaytime=TimeSpan.FromMinutes(10);	
			else m_decaytime=m_maxkilltime+m_maxkilltime;		
		
			new ExpireTimer(this).Start();		
		}
	
		[Constructable]
		public basepeerlesskey( string bossname,string bosstype, Point3D bosslocation,Point3D teleportlocation,Map spawnmap,Rectangle2D peerlessroom) : base( 4110 )
		{
			GenID();//generate a unique (sorta) ID for this key
			
			Hue=1150;			
			
			m_bossname=bossname;
			m_bosstype=bosstype;
			m_spawnmap=spawnmap;
			m_bosslocation=bosslocation;
			m_teleportlocation=teleportlocation;
			m_peerlessroom=peerlessroom;
			m_active=false;
			m_ismainkey=true;
			m_isreusable=false;
			
			LootType = LootType.Blessed;
			
			m_maxkilltime=TimeSpan.FromHours(2);
			m_maxloottime=TimeSpan.FromMinutes(10);		

			m_party = new List<Mobile>();		
			m_createdate=DateTime.Now;
			
			if(ismainkey)m_decaytime=TimeSpan.FromMinutes(10);	
			else m_decaytime=m_maxkilltime+m_maxkilltime;	

			new ExpireTimer(this).Start();
		}
		
		public basepeerlesskey( Serial serial ) : base( serial )
		{
		}
		
		//simply generates a random double value.  The odds of it matching with an existing key are rather slim
		private void GenID()
		{
			m_keyid=Utility.RandomDouble();
		}
		
		//checks if this key has expired and should be deleted
		public bool CheckDecay(Mobile from)
		{
			if(active)return false; //don't decay if its active!
		
			if((createdate+decaytime)<DateTime.Now)
			{
				if(from!=null)from.SendMessage("The key has expired!");
				this.Delete();
				return true;
			}
		
		return false;
		}
		
		public void SpawnPeerless(Mobile from)
		{
			if(m_active)return;
			//here the peerless has actually already been spawned, and everyone has been teleported - this just sets up the timer stuff and region
			
					
			m_region = new PeerlessRegion(bossname,spawnmap,peerlessroom);//set region up
			m_region.Register();
				
			m_spawndate = DateTime.Now;
			m_active=true;
					
			m_Timer = new InternalTimer(this);
			m_Timer.Start();
		}
		
		//checks wether or not they are in the peerless room
		public bool isinroom(Mobile totest)
		{
			//if they're logged in..
			if(totest.Map!=Map.Internal)return(totest.Map==spawnmap && totest.Location.X>peerlessroom.Start.X && totest.Location.Y>peerlessroom.Start.Y && totest.Location.X<peerlessroom.End.X && totest.Location.Y<peerlessroom.End.Y);
			//if they're logged out (still have to check if their log out location is the peerless room)
			return(totest.LogoutMap==spawnmap && totest.LogoutLocation.X>peerlessroom.Start.X && totest.LogoutLocation.Y>peerlessroom.Start.Y && totest.LogoutLocation.X<peerlessroom.End.X && totest.LogoutLocation.Y<peerlessroom.End.Y);
		}
		
		
		//note: DO NOT call this with the spare keys - the mob is already in the array at this point, spare key shoudl use its own teleport call
		public void AddPartyMember(Mobile member,bool rescalehp,bool doteleport)
		{
			if(member==null)return;
			
			if(doteleport)
			{
				if(member is PlayerMobile)BaseCreature.TeleportPets( member, this.teleportlocation,  this.spawnmap );
				member.MoveToWorld( this.teleportlocation, this.spawnmap );	
			}
			
			
			//scale peerless hp based on amount of people
			if(m_peerlessboss!=null && m_peerlessboss is BaseCreature && rescalehp)
			{
				int increment = m_peerlessboss.HitsMax/15;
				if(increment>55000)increment=55000;
								
				((BaseCreature)m_peerlessboss).IncHitsMax(increment);
			}		

			
			m_party.Add(member);
			m_partycount++;
			
			m_Table[member]=member; 
			
			Map map = member.Map;
		}
		
		//check if mob is in peerless party
		public bool IsInParty(Mobile from)
		{
			for(int i=0;i<m_party.Count;i++)
			{
				if(m_party[i] is PlayerMobile)
				{
					PlayerMobile pm = m_party[i] as PlayerMobile;
				
					if(pm==from)return true;
				}
			
			}
		
		
		return false;
		}
		
		
		
		public void SendPartyMessage(string Message)
		{
			for(int i=0;i<m_party.Count;i++)
			{			
				if(m_party[i] is PlayerMobile)
				{		
					PlayerMobile person = m_party[i] as PlayerMobile;
					
					if(isinroom(person))person.SendMessage(Message);  //only send the message if the person is in the peerless room
				}						
			}
		}		
		
		
		public void EjectParty()
		{
			 if(m_originalmap==null)return;
			 m_region.Unregister();
		
			for(int i=0;i<m_party.Count;i++)
			{			
				if(m_party[i] is Mobile)
				{		
					Mobile mob = m_party[i] as Mobile;
					
					
					if(m_Table.Contains( mob ))m_Table.Remove( mob ); 				//remove from hash table
					
					//if person is not in room we don't eject them
					if(!isinroom(mob))continue;
					
					
					//move pets:
					
					if(mob is PlayerMobile)	BaseCreature.TeleportPets( mob, m_originallocation, m_originalmap,false,150,true );//get all pets within 150 tile range (should cover all boss room sizes)
					
					//move to original location:
					
					if(mob.Map!=Map.Internal)mob.MoveToWorld( m_originallocation,m_originalmap ); 	 //make sure they arn't logged out
					else
					{
						mob.LogoutLocation=m_originallocation;
						mob.LogoutMap=m_originalmap;
					}
					
				}						
			}
		}		
		
		//returns wether or not they are doing a peerless boss
		public bool isdoingpeerless(Mobile totest)
		{
		return m_Table.Contains( totest );
		}
		
		//this deletes/unhides the key depending on wether or not its reusable
		public void resetkey()
		{
			if(isreusable)
			{
			oktosummon=false;
			Visible=true;
			m_active=false;
			Movable=true;
			m_party.Clear();
			m_partycount=0;
			GenID();//generate a unique (sorta) ID for this key
			if(lastuser!=null)lastuser.AddToBackpack(this);
			}
			else this.Delete();		
		}
	
	//expire timer
	
	//end expire timer
	public class ExpireTimer : Timer
	{
	basepeerlesskey Owner;
		public ExpireTimer(basepeerlesskey key) : base( TimeSpan.FromSeconds( 1 ), TimeSpan.FromSeconds( 5.0 ) )
		{
		Owner=key;
		Priority=TimerPriority.FiveSeconds;
		}
		
		protected override void OnTick()
		{
			if(Owner.Deleted || Owner==null)
			{
			Stop();
			return;
			}
			
		Owner.CheckDecay(Owner.RootParentEntity is Mobile ? (Mobile)Owner.RootParentEntity : null);			
		Owner.InvalidateProperties();		
		}
	
	}
	
		
	//start internal timer		
	public class InternalTimer : Timer
	{
	DateTime m_lastwarning;
	DateTime m_deathdate;
	bool died=false;
	basepeerlesskey m_key;
	
		public InternalTimer(basepeerlesskey key) : base( TimeSpan.FromSeconds( 1 ), TimeSpan.FromSeconds( 10.0 ) )
		{
		m_key=key;
		
		Priority = TimerPriority.FiveSeconds;
		}	
		
		
		protected override void OnTick()
		{
			if(m_key==null)//sanity check
			{
			Console.WriteLine("Warning: Peerless key prematurely deleted.  Players may be stuck in peerless room.");
			this.Stop();
			return;		
			}			

			//check if it's dead, if yes set death time so we can start counting time to loot
			if(!died && (m_key.peerlessboss==null || !m_key.peerlessboss.Alive))
			{
			died=true;
			m_deathdate=DateTime.Now;
			
			StringBuilder msg = new StringBuilder();
						
			string timestr="";
			if(m_key.m_maxloottime.Days>0)timestr=String.Concat(m_key.m_maxloottime.Days," day(s) and ");
			if(m_key.m_maxloottime.Hours>0)timestr=String.Concat(timestr,String.Concat(m_key.m_maxloottime.Hours," hour(s) and "));
			timestr=String.Concat(timestr,String.Concat(m_key.m_maxloottime.Minutes," minutes"));		
		
			msg.AppendFormat("The boss is now dead, you will be ejected in {0}",timestr);			
			
			SendPartyMessage(msg.ToString());
			}			
			
			//check if time is up
			if((DateTime.Now-m_key.m_spawndate)>m_key.m_maxkilltime && !died)
			{
			SendPartyMessage("Time is up!");

			m_key.EjectParty();
			m_key.peerlessboss.Delete();
			m_key.resetkey();
			this.Stop();
			}
			
			//check if loot time is up
			if((DateTime.Now-m_deathdate)>m_key.m_maxloottime && died)
			{		
			SendPartyMessage("Looting time is up!");
			
			m_key.EjectParty();
			if(m_key.peerlessboss!=null && m_key.peerlessboss.Corpse!=null)m_key.peerlessboss.Corpse.Delete();	
			
			
			m_key.resetkey();
			this.Stop();
			}
			
			
			//warn how much time there is left
			if((DateTime.Now-m_lastwarning)>TimeSpan.FromMinutes(10) && m_key.peerlessboss!=null && !died)
			{
			m_lastwarning=DateTime.Now;
			
			StringBuilder msg = new StringBuilder();
			
			TimeSpan timeleft = (m_key.m_maxkilltime-(DateTime.Now-m_key.m_spawndate));
			
			string timestr="";
			if(timeleft.Days>0)timestr=String.Concat(timeleft.Days," day(s) and ");
			if(timeleft.Hours>0)timestr=String.Concat(timestr,String.Concat(timeleft.Hours," hour(s) and "));
			timestr=String.Concat(timestr,String.Concat(timeleft.Minutes," minutes"));				
			
			msg.AppendFormat("You have {0} left to kill the boss!",timestr);
			
			m_key.SendPartyMessage(msg.ToString());
			}
	
		}
		
		private void SendPartyMessage(string Message)
		{		
		m_key.SendPartyMessage(Message);
		}	
		
	}
	//end internal timer
		
		
		public override void OnDoubleClick( Mobile from )
		{	
		oktosummon=false;		
		if(CheckDecay(from))return;
		
			if(!IsChildOf( from.Backpack ))
			{
			from.SendMessage("This must be in your backpack for you to use it");
			return;
			}
			
			if(m_active)
			{
			from.SendMessage("This key is already active.");
			return;
			}		
					
			if(ismainkey)
			{		
			
				if(m_Table.Contains( from ))
				{
				from.SendMessage("You are already doing a peerless boss!");
				return;
				}			
			

				foreach ( Item i in World.Items.Values )
				{
					if ( i is basepeerlesskey )
					{	
						/*if there is an active key where the boss type AND spawn location are the same, we assume it's the same peerless boss.
						This leaves room for customizability as the same boss can be done at same time, like in a different room or something */
						if(((basepeerlesskey)i).bosstype==bosstype && ((basepeerlesskey)i).active==true && ((basepeerlesskey)i).bosslocation==bosslocation)
						{
							if(((basepeerlesskey)i).keyid==keyid) //we'll let them use this key since the ID matches, but we wont' summon another boss!  Also, we'll add the party to the original key, not this one
							{
							((basepeerlesskey)i).oktosummon=true;
							oktosummon=true;
							from.CloseGump( typeof( basepeerlesskeyGump ) );
							from.SendGump( new basepeerlesskeyGump( from, ((basepeerlesskey)i), true,this) );		
							return;
							}
							{
							//someone is already doing the boss and the ID of this one does not match, so we have to wait
							from.SendMessage( String.Concat(String.Concat("A Party is Already in Battle With ",m_bossname), ". Please try again later") );
							return;							
							}
						}
					}
				}				
				oktosummon=true;
				from.CloseGump( typeof( basepeerlesskeyGump ) );
				from.SendGump( new basepeerlesskeyGump( from, this, false,this) );
			}
			else   //only teleport the user, and don't do a summoning
			{
			//make sure that this sub key is for a spawn that is still active
			
				bool isvalid=false;
				basepeerlesskey mainkey=null;
				foreach ( Item i in World.Items.Values )
				{
					if ( i is basepeerlesskey )
					{			
						if(((basepeerlesskey)i).bosstype==bosstype && ((basepeerlesskey)i).active==true && ((basepeerlesskey)i).ismainkey==true && ((basepeerlesskey)i).keyid==keyid)
						{
						//found a main key with matching ID, so we deem this key valid
						mainkey=i as basepeerlesskey;
						isvalid=true;
						break;
						}				
					}
				}
			
				if(isvalid)			
				{
					if(!mainkey.IsInParty(from))
					{
					from.SendMessage("You must be part of the peerless party to use this key!");
					return;
					}
					if(mainkey.isinroom(from))
					{
					from.SendMessage("You are already in the peerless room!");
					return;					
					}
					
					
				if(from is PlayerMobile)BaseCreature.TeleportPets( from, teleportlocation,  spawnmap,false,6,true );
				from.MoveToWorld(teleportlocation,spawnmap);
				if(!isreusable)this.Delete();
				}
				else
				{
				from.SendMessage("This key has expired or is invalid");
				}
			
			}
		
			
		}
		
		public override void Serialize( GenericWriter writer )
		{
			base.Serialize( writer );
			writer.Write( (int) 0 );
			
			writer.Write(m_keyid);
			writer.WriteDeltaTime(m_createdate);
			writer.Write((TimeSpan)m_decaytime);			
			writer.Write(m_bossname);
			writer.Write(m_bosstype);
			writer.Write((Point3D)m_bosslocation);
			writer.Write((Point3D)m_teleportlocation);
			writer.Write((Rectangle2D)m_peerlessroom);
			
			writer.Write((Map)m_spawnmap);
			writer.Write((TimeSpan)m_maxkilltime);
			writer.Write((TimeSpan)m_maxloottime);
			
			writer.Write((bool)m_active);
			writer.WriteDeltaTime(m_spawndate);
			writer.Write((Point3D)m_originallocation);
			writer.Write((Map)m_originalmap);
			
			writer.Write((Mobile)m_peerlessboss);
			writer.Write((bool)m_ismainkey);
			writer.Write((bool)m_isreusable);		
			writer.Write((Mobile)m_lastuser);
				
			writer.Write((int)m_partycount);			
			for(int i=0;i<m_partycount;i++)
			{
				object o = m_party[i];

				if ( o is Mobile ) writer.Write( (Mobile)o );
			}
			
		}
		
		public override void Deserialize( GenericReader reader )
		{
			base.Deserialize( reader );
			int version = reader.ReadInt();
			
			m_keyid=reader.ReadDouble();
			m_createdate=reader.ReadDeltaTime();
			m_decaytime=reader.ReadTimeSpan();
			m_bossname=reader.ReadString();
			m_bosstype=reader.ReadString();
			m_bosslocation=reader.ReadPoint3D();
			m_teleportlocation=reader.ReadPoint3D();
			m_peerlessroom=reader.ReadRect2D();
			
			m_spawnmap=reader.ReadMap();
			m_maxkilltime=reader.ReadTimeSpan();
			m_maxloottime=reader.ReadTimeSpan();
			
			m_active=reader.ReadBool();			
			m_spawndate=reader.ReadDeltaTime();					
			m_originallocation= reader.ReadPoint3D();
			m_originalmap= reader.ReadMap();
			
			
			m_peerlessboss = reader.ReadMobile();
			m_ismainkey=reader.ReadBool();
			m_isreusable=reader.ReadBool();
			m_lastuser=reader.ReadMobile();
			
			int tmppartycount = reader.ReadInt();	 
			m_partycount=0; //the actual value is recreated when players are added back into the array so we want it to be 0 here
			
			m_party = new List<Mobile>();			
			
			for(int i=0;i<tmppartycount;i++)
			{
			Mobile partymember = reader.ReadMobile();

			AddPartyMember(partymember,false,false);  //add back in array
			}

			if(m_active)
			{
			m_Timer = new InternalTimer(this);
			m_Timer.Start();
			m_region = new PeerlessRegion(bossname,spawnmap,peerlessroom);//set region up
			m_region.Register();
			}
			else CheckDecay(null);					
			
			new ExpireTimer(this).Start();
		}
	}

	
	
		public class basepeerlesskeyGump : Gump
		{
		private Mobile m_Mobile;
		private basepeerlesskey m_Deed;
		private basepeerlesskey m_secondarykey;		
		private bool m_dontsummon;
		
		public basepeerlesskeyGump( Mobile from, basepeerlesskey deed,bool dontsummon,basepeerlesskey secondarykey ) : base( 30, 20 )
		{
			m_Mobile = from;
			m_Deed = deed;
			m_dontsummon=dontsummon;
			m_secondarykey=secondarykey;
						
			//Account a = from.Account as Account;
			
			AddImageTiled(  54, 33, 369, 400, 2624 );
			AddAlphaRegion( 54, 33, 369, 400 );
			AddImageTiled( 416, 39, 44, 389, 203 );			
			AddImage( 97, 49, 9005 );
			AddImageTiled( 58, 39, 29, 390, 10460 );
			AddImageTiled( 412, 37, 31, 389, 10460 );
			AddLabel( 140, 60, 0x34, String.Concat("Destroy ",m_Deed.bossname) );
			AddLabel( 140, 104, 0x36, String.Format("Continuing will prompt your party to follow."));
			AddLabel( 140, 128, 0x36, String.Format("You have {0} minutes to slay the boss.",(deed.maxkilltime.TotalSeconds/60)) );			
			AddImage( 430, 9, 10441);
			AddImageTiled( 40, 38, 17, 391, 9263 );
			AddImage( 6, 25, 10421 );
			AddImage( 34, 12, 10420 );
			AddImageTiled( 94, 25, 342, 15, 10304 );
			AddImageTiled( 40, 427, 415, 16, 10304 );
			AddImage( -10, 314, 10402 );
			AddImage( 56, 180, 10411 );
			AddImage( 136, 84, 96 );
			AddImage( 215, 190, 5536 );

			AddButton( 225, 390, 0xF7, 0xF8, 1, GumpButtonType.Reply, 0 );
		}
		
		public override void OnResponse( NetState state, RelayInfo info )
		{		
			Mobile from = state.Mobile;
							
			 if(m_Deed==null || from==null ||m_Deed.spawnmap==null)return;//sanity check
			
			 if(m_Deed.CheckDecay(from))return;
			
			//security check (that value is set to true when the deed is double clicked and gump is sent - avoids locally somewhow generating the OK packet sequence without the gump)
			if(!m_Deed.oktosummon && m_secondarykey!=null && !m_secondarykey.oktosummon)return;
			m_Deed.oktosummon=false;
			if(m_secondarykey!=null)m_secondarykey.oktosummon=false;
			
			switch( info.ButtonID )
			{
				case 0:
					{
						from.CloseGump( typeof( basepeerlesskeyGump ) );
						break;
					}
					case 1: //Case uses the ActionIDs defenied above. Case 1 defenies the actions for the button with the action id 1 
					{

							if(!m_dontsummon)  //only summon the boss if this is the first key of that ID used (the global loop that calls the gump determins this)
							{
								//first we spawn the peerless boss
								object o = Activator.CreateInstance( SpawnerType.GetType( (string)m_Deed.bosstype ) );
								
								if(o==null || (!(o is Mobile)))
								{
									from.SendMessage("Specified peerless type not found.  Contact GM to have key checked for error.");
									return; //cancel if type does not exist
								}
									m_Deed.peerlessboss = (Mobile)o;
									m_Deed.peerlessboss.MoveToWorld( m_Deed.bosslocation, m_Deed.spawnmap);							
								}
								
								//this is where everyone gets ejected when boss is over (even on a secondary key, this has to be set for when it does the party eheck - people will be ejected to original key's location, however)
								m_Deed.originallocation=from.Location;
								m_Deed.originalmap=from.Map;
						    
					
					
								Party party = Party.Get( from );
								
								if( party != null )
								{
									for( int i = 0; i < party.Count; i++ )
									{
										Mobile m = party[ i ].Mobile;

										if( Utility.InRange( m.Location, m_Deed.originallocation, 10 ) )
										{	
											
											if(m_Deed.isdoingpeerless(m)) //don't allow to go in more then one peerless at once
											{
												m.SendMessage("A party member has used a peerless key, but you are busy with another peerless, and remain here.");
												continue;
											}
											else
											{
												//send gump:
												m.CloseGump(typeof(PeerlessPartyGump));
												if(from!=m)	m.SendGump(new PeerlessPartyGump( from,  m, m_Deed ));
												else m_Deed.AddPartyMember(from,m_dontsummon,true);
											}
										}
									}
								}
								else
								{			
									m_Deed.AddPartyMember(from,m_dontsummon,true);
								}						
					
								//start timer (only if we're summoning a new boss)
								if(!m_dontsummon)
								{
									m_Deed.SpawnPeerless(from); 
									m_Deed.Movable=false;
									m_Deed.Visible=false; //fakely delete the deed (it will actually be used to keep track of the timers and stuff - will be deleted later)
									m_Deed.lastuser=from;
									m_Deed.MoveToWorld(m_Deed.originallocation,m_Deed.originalmap); //we put it at their feet, just in case they delete their charactor for w/e reason (would screw up timer)
								}
								else if(m_secondarykey!=null) //delete the secondary key which was used to teleport another party to existing spawn
								{
									if(!m_secondarykey.isreusable)m_secondarykey.Delete();
								}
						
					break;
					}
			}
		}
	}
	
	
	
	public class PeerlessPartyGump : Gump
	{
		private Mobile m_Leader;
		private Mobile m_Member;
		private basepeerlesskey m_key;
		DateTime expiry;

		public PeerlessPartyGump( Mobile leader, Mobile member, basepeerlesskey key ) : base( 150, 50 )
		{
			if(leader==null || member==null || key==null)
			{
				if(member!=null)member.CloseGump(typeof(PeerlessPartyGump));
				return;
			}
			
			m_Leader = leader;
			m_Member = member;
			m_key=key;
			expiry=DateTime.Now + TimeSpan.FromMinutes(1); //give 1 minute grace period to use gump

			Closable = false;

			AddPage( 0 );

			AddImage( 0, 0, 3600 );

			AddImageTiled( 0, 14, 15, 200, 3603 );
			AddImageTiled( 380, 14, 14, 200, 3605 );
			AddImage( 0, 201, 3606 );
			AddImageTiled( 15, 201, 370, 16, 3607 );
			AddImageTiled( 15, 0, 370, 16, 3601 );
			AddImage( 380, 0, 3602 );
			AddImage( 380, 201, 3608 );
			AddImageTiled( 15, 15, 365, 190, 2624 );

			AddRadio( 30, 140, 9727, 9730, true, 1 );
			AddHtmlLocalized( 65, 145, 300, 25, 1050050, 0x7FFF, false, false ); // Yes, let's go!

			AddRadio( 30, 175, 9727, 9730, false, 0 );
			AddHtmlLocalized( 65, 178, 300, 25, 1050049, 0x7FFF, false, false ); // No thanks, I'd rather stay here.

			AddHtml( 30, 20, 360, 35, String.Format("<basefont color=\"#FFFFFF\">Another player is inviting you to kill {1}:</basefont>",leader.Name,key.bossname), false, false );

			AddHtmlLocalized( 30, 105, 345, 40, 1050048, 0x5B2D, false, false ); // Do you wish to accept their invitation at this time?

			AddImage( 65, 72, 5605 );

			AddImageTiled( 80, 90, 200, 1, 9107 );
			AddImageTiled( 95, 92, 200, 1, 9157 );

			AddLabel( 90, 70, 1645, leader.Name );

			AddButton( 290, 175, 247, 248, 2, GumpButtonType.Reply, 0 );

			AddImageTiled( 15, 14, 365, 1, 9107 );
			AddImageTiled( 380, 14, 1, 190, 9105 );
			AddImageTiled( 15, 205, 365, 1, 9107 );
			AddImageTiled( 15, 14, 1, 190, 9105 );
			AddImageTiled( 0, 0, 395, 1, 9157 );
			AddImageTiled( 394, 0, 1, 217, 9155 );
			AddImageTiled( 0, 216, 395, 1, 9157 );
			AddImageTiled( 0, 0, 1, 217, 9155 );
		}

		public override void OnResponse( NetState sender, RelayInfo info )
		{
			if ( info.ButtonID == 2 && info.IsSwitched( 1 ) )
			{
				//check a few things first...
				
				Party p = Party.Get( m_Leader );
				
				if(p==null || !p.Contains( m_Member))
				{
					m_Member.SendMessage("You are not part of this party!");
				}				
				else if(DateTime.Now>expiry)
				{
					m_Member.SendMessage("Sorry, you may not enter at this time.  Please be quicker next time!");
				}
				else
				{
					m_key.AddPartyMember(m_Member,true,true);
					m_Leader.SendMessage( "{0} has accepted your invitation", m_Member.Name );
				}
			}
			else
			{
				//Deny:
				m_Member.SendLocalizedMessage( 1050052 ); // You have declined their invitation.
				m_Leader.SendMessage( "{0} has declined your invitation", m_Member.Name );
			}
		}
	}
}




//peerless boss region

namespace Server.Regions
{

	public class PeerlessRegion : GuardedRegion
	{
	
	    public PeerlessRegion(string name,Map map, Rectangle2D area): base(name, map, 70, new Rectangle3D(area.Start.X,area.Start.Y,-300, area.Width, area.Height,600))
        {
		
			Disabled=true;		//disable guards... Don't want those in a peerless room O_o
        }
	
		
        public override bool AllowHousing(Mobile from, Point3D p)
        {
            return false;
        }	

        public override bool CanUseStuckMenu(Mobile m)
        {
	         if(m.Alive)
			 {
	                m.SendMessage("You are alive and the boss is not dead. You have no reason to leave!");
					return false;
			 }
            return true;
        }


        public override void AlterLightLevel(Mobile m, ref int global, ref int personal)
        {
			global = LightCycle.DungeonLevel;
        }		
		

        public override bool OnBeginSpellCast(Mobile from, ISpell s)
        {
		
			//restrict recall type spells
			if(((Spell)s).Info.Name=="Recall")return CantCastRecall(from);
			if(((Spell)s).Info.Name=="Mark")return CantCastRecall(from);
			if(((Spell)s).Info.Name=="Gate Travel")return CantCastRecall(from);
			if(((Spell)s).Info.Name=="Sacred Journey")return CantCastRecall(from);

			return true;
        }		
		
		
		private bool CantCastRecall(Mobile from)
		{
			from.SendMessage("You cannot use this spell here");
			return false;
		}	
	
	}

}