public Point3D GetSpawnPosition(bool requiresurface, int packrange, Point3D packcoord, SpawnPositionInfo spawnpositioning)
		{
			Map map = Map;

			if (map == null)
				return Location;

			// random positioning by default
			SpawnPositionType positioning = SpawnPositionType.Random;
			Mobile trigmob = null;
			string[] positionargs = null;

			if(spawnpositioning != null)
			{
				positioning = spawnpositioning.positionType;
				trigmob = spawnpositioning.trigMob;
				positionargs = spawnpositioning.positionArgs;
			}

			// parse the possible args to the spawn position control keywords
			int fillinc = 1;
			int positionrange = 0;
			string prefix = null;
			ArrayList WayList = null;
			int xinc = 0;
			int yinc = 0;
			int zinc = 0;
			switch (positioning)
			{
				case SpawnPositionType.RowFill:
				case SpawnPositionType.ColFill:
				case SpawnPositionType.Perimeter:
					// syntax XFILL[,inc]
					// syntax YFILL[,inc]
					// syntax EDGE[,inc]
					if (positionargs != null && positionargs.Length > 1)
					{
						try
						{
							fillinc = int.Parse(positionargs[1]);
						}
						catch { }
					}
					break;
				case SpawnPositionType.RelXY:
				case SpawnPositionType.DeltaLocation:
				case SpawnPositionType.Location:
					// syntax RELXY,xinc,yinc[,zinc]
					// syntax XY,x,y[,z]
					// syntax DXY,dx,dy[,dz]
					if (positionargs != null && positionargs.Length > 2)
					{
						try
						{
							xinc = int.Parse(positionargs[1]);
							yinc = int.Parse(positionargs[2]);
						}
						catch { }
					}
					if (positionargs != null && positionargs.Length > 3)
					{
						try
						{
							zinc = int.Parse(positionargs[3]);
						}
						catch { }
					}
					break;
				case SpawnPositionType.Waypoint:
					// syntax WAYPOINT,prefix[,range]
					if (positionargs != null && positionargs.Length > 1)
					{
						prefix = positionargs[1];
					}

					if (positionargs != null && positionargs.Length > 2)
					{
						try
						{
							positionrange = int.Parse(positionargs[2]);
						}
						catch { }
					}

					// find a list of items that match the waypoint prefix
					if (prefix != null)
					{
						WayList = new ArrayList();

						foreach (Item i in World.Items.Values)
						{
							if (i is WayPoint && i.Name != null && i.Name.StartsWith(prefix))
							{
								// add it to the list of items
								WayList.Add(i);
							}
						}
					}
					break;
				case SpawnPositionType.Player:
					// syntax PLAYER[,range]
					if (positionargs != null && positionargs.Length > 1)
					{
						try
						{
							positionrange = int.Parse(positionargs[1]);
						}
						catch { }
					}
					break;
			}

			// Try 10 times to find a Spawnable location.
			// trace profiling indicates that this is a major bottleneck
			for (int i = 0; i < 10; i++)
			{
				// Take the top left position of the spawn box and
				// add a random distance based on the width and
				// height of the spawn area.
				// be careful of the Utility.Random function.  It will fail with a zero arg
				int x = m_X;
				int y = m_Y;
				int z = Z;

				int defaultZ = this.Z;
				if (packrange >= 0 && packcoord != Point3D.Zero)
				{
					defaultZ = packcoord.Z;
				}

				try
				{
					if (packrange >= 0 && packcoord != Point3D.Zero)
					{
						// find a random coord relative to the packcoord
						x = packcoord.X - packrange + Utility.Random(packrange * 2 + 1);
						y = packcoord.Y - packrange + Utility.Random(packrange * 2 + 1);
					}
					else
						if (m_Region != null && HasRegionPoints(m_Region))	// 2004.02.08 :: Omega Red
						{
							// if region spawning is selected then use that to find an x,y loc instead of the spawn box
							Point2D p = GetRandomRegionPoint(m_Region);
							x = p.X;
							y = p.Y;
						}
						else
						{
							switch (positioning)
							{
								case SpawnPositionType.Random:

									if (m_Width > 0)
										x = m_X + Utility.Random(m_Width + 1);
									if (m_Height > 0)
										y = m_Y + Utility.Random(m_Height + 1);
									break;
								case SpawnPositionType.RelXY:

									x = mostRecentSpawnPosition.X + xinc;
									y = mostRecentSpawnPosition.Y + yinc;
									defaultZ = mostRecentSpawnPosition.Z + zinc;
									break;
								case SpawnPositionType.DeltaLocation:

									x = this.X + xinc;
									y = this.Y + yinc;
									defaultZ = this.Z + zinc;
									break;
								case SpawnPositionType.Location:

									x = xinc;
									y = yinc;
									defaultZ = zinc;
									break;
								case SpawnPositionType.RowFill:

									x = mostRecentSpawnPosition.X + fillinc;
									y = mostRecentSpawnPosition.Y;

									if (x < m_X)
									{
										x = m_X;
									}

									if (y < m_Y)
									{
										y = m_Y;
									}

									if (x > m_X + m_Width)
									{
										x = m_X + (x - m_X - m_Width - 1);
										y++;
									}

									if (y > m_Y + m_Height)
									{
										y = m_Y;
									}

									break;
								case SpawnPositionType.ColFill:

									x = mostRecentSpawnPosition.X;
									y = mostRecentSpawnPosition.Y + fillinc;

									if (x < m_X)
									{
										x = m_X;
									}

									if (y < m_Y)
									{
										y = m_Y;
									}

									if (y > m_Y + m_Height)
									{
										y = m_Y + (y - m_Y - m_Height - 1);
										x++;
									}

									if (x > m_X + m_Width)
									{
										x = m_X;
									}

									break;
								case SpawnPositionType.Perimeter:

									x = mostRecentSpawnPosition.X;
									y = mostRecentSpawnPosition.Y;

									// if the point is not on the perimeter, reset it to the corner
									if (x != m_X && x != m_X + m_Width && y != m_Y && y != m_Y + m_Height)
									{
										x = m_X;
										y = m_Y;
									}

									if (y == m_Y && x < m_X + m_Width) x += fillinc;
									else
									if (y == m_Y + m_Height && x > m_X) x -= fillinc;
									else
									if (x == m_X && y > m_Y ) y -= fillinc;
									else
									if (x == m_X + m_Width && y < m_Y + m_Height) y += fillinc;

									if (x > m_X + m_Width)
									{
										x = m_X + m_Width;
									}

									if (y > m_Y + m_Height)
									{
										y = m_Y + m_Height;
									}

									if (x < m_X)
									{
										x = m_X;
									}

									if (y < m_Y)
									{
										y = m_Y;
									}

									break;
								case SpawnPositionType.Player:

									if (trigmob != null)
									{
										x = trigmob.Location.X;
										y = trigmob.Location.Y;
										if (positionrange > 0)
										{
											x += Utility.Random(positionrange*2 + 1) - positionrange;
											y += Utility.Random(positionrange * 2 + 1) - positionrange;
										}
									}
									break;
								case SpawnPositionType.Waypoint:
									
									// pick an item randomly from the waylist
									if (WayList != null && WayList.Count > 0)
									{
										int index = Utility.Random(WayList.Count);
										Item waypoint = (Item)WayList[index];
										if (waypoint != null)
										{
											x = waypoint.Location.X;
											y = waypoint.Location.Y;
											if (positionrange > 0)
											{
												x += Utility.Random(positionrange * 2 + 1) - positionrange;
												y += Utility.Random(positionrange * 2 + 1) - positionrange;
											}
										}
									}
									break;
							}

							mostRecentSpawnPosition = new Point3D(x, y, defaultZ);
						}

					// try to find a valid spawn location using the z coord of the spawner
					// relax the normal surface requirement for mobiles if the flag is set
					bool fit = false;
					if (requiresurface)
					{
						fit = Map.CanSpawnMobile(x, y, defaultZ);
					}
					else
					{
						fit = Map.CanFit(x, y, defaultZ, SpawnFitSize, true, false, false);
					}

					// if that fails then try to find a valid z coord
					if (fit)
					{

						return new Point3D(x, y, defaultZ);
					}
					else
					{

						z = Map.GetAverageZ(x, y);

						if (requiresurface)
						{
							fit = Map.CanSpawnMobile(x, y, z);
						}
						else
							fit = Map.CanFit(x, y, z, SpawnFitSize, true, false, false);

						if (fit)
						{
							return new Point3D(x, y, z);
						}
					}

				}
				catch { }
			}

			if (packrange >= 0 && packcoord != Point3D.Zero)
			{
				return packcoord;
			}
			else
			{
				return this.Location;
			}
		}
		// spawn an individual entry by index
		public bool Spawn(int index, bool smartspawn, int packrange, Point3D packcoord)
		{

			Map map = this.Map;

			// Make sure everything is ok to spawn an object
			if ((map == null) ||
				(map == Map.Internal) ||
				(m_SpawnObjects == null) ||
				(m_SpawnObjects.Count == 0) ||
				(index < 0) ||
				(index >= m_SpawnObjects.Count)
				)
				return false;

			// Remove any spawns that don't belong to the spawner any more.
			Defrag(false);

			// Get the spawn object at the required index
			SpawnObject TheSpawn = m_SpawnObjects[index] as SpawnObject;

			// Check if the object retrieved is a valid SpawnObject
			if (TheSpawn != null)
			{
				// check the nextspawn time to see if it is available
				if (TheSpawn.NextSpawn > DateTime.Now)
					return false;

				int CurrentCreatureMax = TheSpawn.MaxCount;
				int CurrentCreatureCount = TheSpawn.SpawnedObjects.Count;

				// Check that the current object to be spawned has not reached its maximum allowed
				// and make sure that the maximum spawner count has not been exceeded as well
				if ((CurrentCreatureCount >= CurrentCreatureMax) ||
					(TotalSpawnedObjects >= m_Count))
				{
					return false;
				}

				// check for string substitions
				string substitutedtypeName = BaseXmlSpawner.ApplySubstitution(this, this, m_mob_who_triggered, TheSpawn.TypeName);

				// random positioning is the default
				SpawnPositionInfo spawnpositioning = null;

				// require valid surfaces by default
				bool requiresurface = true;

				// parse the # function specification for the entry
				while (substitutedtypeName.StartsWith("#"))
				{
					string[] args = BaseXmlSpawner.ParseSemicolonArgs(substitutedtypeName, 2);

					if (args.Length > 0)
					{
						// parse any comma args
						string[] keyvalueargs = BaseXmlSpawner.ParseCommaArgs(args[0], 10);

						if (keyvalueargs.Length > 0)
						{

							switch (keyvalueargs[0])
							{
								case "#XFILL":
									spawnpositioning = new SpawnPositionInfo(SpawnPositionType.RowFill, m_mob_who_triggered, keyvalueargs);
									break;
								case "#YFILL":
									spawnpositioning = new SpawnPositionInfo(SpawnPositionType.ColFill, m_mob_who_triggered, keyvalueargs);
									break;
								case "#EDGE":
									spawnpositioning = new SpawnPositionInfo(SpawnPositionType.Perimeter, m_mob_who_triggered, keyvalueargs);
									break;
								case "#PLAYER":
									spawnpositioning = new SpawnPositionInfo(SpawnPositionType.Player, m_mob_who_triggered, keyvalueargs);
									break;
								case "#WAYPOINT":
									spawnpositioning = new SpawnPositionInfo(SpawnPositionType.Waypoint, m_mob_who_triggered, keyvalueargs);
									break;
								case "#RELXY":
									spawnpositioning = new SpawnPositionInfo(SpawnPositionType.RelXY, m_mob_who_triggered, keyvalueargs);
									break;
								case "#DXY":
									spawnpositioning = new SpawnPositionInfo(SpawnPositionType.DeltaLocation, m_mob_who_triggered, keyvalueargs);
									break;
								case "#XY":
									spawnpositioning = new SpawnPositionInfo(SpawnPositionType.Location, m_mob_who_triggered, keyvalueargs);
									break;
								case "#CONDITION":
									// test the specified condition string
									// syntax is #CONDITION,proptest
									// reparse with only one arg after the comma, this allows property tests that use commas as well
									string[] ckeyvalueargs = BaseXmlSpawner.ParseCommaArgs(args[0], 2);
									if (ckeyvalueargs.Length > 1)
									{
										// dont spawn if it fails the test
										if(!BaseXmlSpawner.CheckPropertyString(this, this, ckeyvalueargs[1], m_mob_who_triggered, out this.status_str)) return false;
										
									} else
										{
											this.status_str = "invalid #CONDITION specification: " + args[0]; 
										}
									break;
								default:
									this.status_str = "invalid # specification: " + args[0];
									break;
							}
						}
					}

					// get the rest of the spawn entry
					if (args.Length > 1)
					{
						substitutedtypeName = args[1].Trim();
					}
					else
					{
						substitutedtypeName = String.Empty;
					}
				}


				if (substitutedtypeName.StartsWith("*"))
				{
					requiresurface = false;
					substitutedtypeName = substitutedtypeName.TrimStart('*');
				}

				TheSpawn.RequireSurface = requiresurface;

				string typeName = BaseXmlSpawner.ParseObjectType(substitutedtypeName);

				if (BaseXmlSpawner.IsTypeOrItemKeyword(typeName))
				{
					string status_str = null;

					bool completedtypespawn = BaseXmlSpawner.SpawnTypeKeyword(this, TheSpawn, typeName, substitutedtypeName, requiresurface, spawnpositioning,
						m_mob_who_triggered, this.Location, this.Map, new XmlGumpCallback(SpawnerGumpCallback), out status_str);

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

					if (completedtypespawn)
					{
						// successfully spawned the keyword
						// note that returning true means that Spawn will assume that it worked and will not try to respawn something else
						// added the duration timer that begins on spawning
						DoTimer2(m_Duration);

						InvalidateProperties();

						return true;
					}
					else
					{
						return false;
					}
				}
				else
				{

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

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

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

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

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

									return true;
								}

								Mobile m = (Mobile)o;

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

								Point3D loc;

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

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

								m.MoveToWorld(loc, map);

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

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

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

								// if the object has an OnSpawned method, then invoke it
								if (!smartspawn)
								{
									m.OnAfterSpawn();
								}

								// apply the parsed arguments from the typestring using setcommand
								// be sure to do this after setting map and location so that errors dont place the mob on the internal map
								string status_str;

								BaseXmlSpawner.ApplyObjectStringProperties(this, substitutedtypeName, m, m_mob_who_triggered, this, out status_str);

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

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

								InvalidateProperties();

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

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

								string status_str;

								BaseXmlSpawner.AddSpawnItem(this, TheSpawn, item, this.Location, map, m_mob_who_triggered, requiresurface, spawnpositioning, substitutedtypeName, smartspawn, out status_str);

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

								InvalidateProperties();

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

								return true;
							}
						}
						catch (Exception ex) { Console.WriteLine("When spawning {0}, {1}", o, ex); }
					}
					else
					{
						this.status_str = "invalid type specification: " + typeName;
						return true;
					}
				}
			}
			return false;
		}