Переглянути джерело

BETA: Initial implementation of core support for zone-based spawn
* Reviewed by: UnAfraid, Zoey76

VlLight 11 роки тому
батько
коміт
50be8ca600

+ 4 - 0
L2J_Server_BETA/java/com/l2jserver/gameserver/GeoEngine.java

@@ -696,6 +696,10 @@ public class GeoEngine extends GeoData
 	 */
 	private static void nInitGeodata()
 	{
+		if (Config.GEODATA == 0)
+		{
+			return;
+		}
 		final File file = new File(Config.GEODATA_DIR, "geo_index.txt"); // UnAfraid: FIXME: Migrate this TXT to XML and auto port the TXT into XML if it exists but XML doesn't.
 		if (!file.exists())
 		{

+ 38 - 12
L2J_Server_BETA/java/com/l2jserver/gameserver/ai/L2AttackableAI.java

@@ -350,8 +350,25 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
 				{
 					if (npc.getSpawn() != null)
 					{
+						int x, y, z;
+						
+ 						// Territory based spawn
+ 						if ((npc.getSpawn().getX() == 0) && (npc.getSpawn().getY() == 0))
+ 						{
+							x = npc.getSpawn().getX(npc);
+							y = npc.getSpawn().getY(npc);
+							z = npc.getSpawn().getZ(npc);
+						}
+						// Npc with fixed coords
+						else
+						{
+							x = npc.getSpawn().getX();
+							y = npc.getSpawn().getY();
+							z = npc.getSpawn().getZ();
+						}
+						
 						final int range = Config.MAX_DRIFT_RANGE;
-						if (!npc.isInsideRadius(npc.getSpawn().getX(), npc.getSpawn().getY(), npc.getSpawn().getZ(), range + range, true, false))
+						if (!npc.isInsideRadius(x, y, z, range + range, true, false))
 						{
 							intention = AI_INTENTION_ACTIVE;
 						}
@@ -642,8 +659,8 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
 				}
 			}
 			
-			// If NPC with random coord in territory
-			if ((npc.getSpawn().getX() == 0) && (npc.getSpawn().getY() == 0))
+			// If NPC with random coord in territory - old method (for backward compartibility)
+			if ((npc.getSpawn().getX() == 0) && (npc.getSpawn().getY() == 0) && (npc.getSpawn().getSpawnTerritory() == null))
 			{
 				// Calculate a destination point in the spawn area
 				int p[] = TerritoryTable.getInstance().getRandomPoint(npc.getSpawn().getLocationId());
@@ -670,10 +687,19 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
 			}
 			else
 			{
-				// If NPC with fixed coord
-				x1 = npc.getSpawn().getX();
-				y1 = npc.getSpawn().getY();
-				z1 = npc.getSpawn().getZ();
+				// New territory based spawn - obtain last spawn point
+				if (npc.getSpawn().getX() == 0 && npc.getSpawn().getY() == 0)
+				{
+					x1 = npc.getSpawn().getX(npc);
+					y1 = npc.getSpawn().getY(npc);
+					z1 = npc.getSpawn().getZ(npc);
+				}
+				else // If NPC with fixed coord
+				{
+					x1 = npc.getSpawn().getX();
+					y1 = npc.getSpawn().getY();
+					z1 = npc.getSpawn().getZ();
+				}				
 				
 				if (!npc.isInsideRadius(x1, y1, 0, range, false, false))
 				{
@@ -681,11 +707,11 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
 				}
 				else
 				{
-					x1 = Rnd.nextInt(range * 2); // x
-					y1 = Rnd.get(x1, range * 2); // distance
-					y1 = (int) Math.sqrt((y1 * y1) - (x1 * x1)); // y
-					x1 += npc.getSpawn().getX() - range;
-					y1 += npc.getSpawn().getY() - range;
+					int deltaX = Rnd.nextInt(range * 2); // x
+					int deltaY = Rnd.get(deltaX, range * 2); // distance
+					deltaY = (int) Math.sqrt(deltaY * deltaY - deltaX * deltaX); // y
+					x1 = deltaX + x1 - range;
+					y1 = deltaY + y1 - range;
 					z1 = npc.getZ();
 				}
 			}

+ 190 - 39
L2J_Server_BETA/java/com/l2jserver/gameserver/datatables/SpawnTable.java

@@ -33,16 +33,22 @@ import javolution.util.FastSet;
 
 import com.l2jserver.Config;
 import com.l2jserver.L2DatabaseFactory;
+import com.l2jserver.gameserver.engines.DocumentParser;
 import com.l2jserver.gameserver.instancemanager.DayNightSpawnManager;
+import com.l2jserver.gameserver.instancemanager.ZoneManager;
 import com.l2jserver.gameserver.model.L2Spawn;
+import com.l2jserver.gameserver.model.StatsSet;
 import com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate;
 import com.l2jserver.gameserver.model.interfaces.IL2Procedure;
 
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
 /**
  * Spawn data retriever.
  * @author Zoey76
  */
-public final class SpawnTable
+public final class SpawnTable extends DocumentParser
 {
 	private static final Logger _log = Logger.getLogger(SpawnTable.class.getName());
 	// SQL
@@ -51,6 +57,8 @@ public final class SpawnTable
 	
 	private static final Map<Integer, Set<L2Spawn>> _spawnTable = new FastMap<Integer, Set<L2Spawn>>().shared();
 	
+	private int _xmlSpawnCount = 0;
+	
 	protected SpawnTable()
 	{
 		load();
@@ -59,6 +67,7 @@ public final class SpawnTable
 	/**
 	 * Wrapper to load all spawns.
 	 */
+	@Override
 	public void load()
 	{
 		if (!Config.ALT_DEV_NO_SPAWNS)
@@ -71,6 +80,124 @@ public final class SpawnTable
 				fillSpawnTable(true);
 				_log.info(getClass().getSimpleName() + ": Loaded " + (_spawnTable.size() - spawnCount) + " custom npc spawns.");
 			}
+			
+			// Load XML list
+			parseDirectory("data/spawnlist");
+			_log.info(getClass().getSimpleName() + ": Loaded " + _xmlSpawnCount + " npc spawns from XML.");
+		}
+	}
+	
+	private boolean checkTemplate(int npcId)
+	{
+		L2NpcTemplate npcTemplate = NpcTable.getInstance().getTemplate(npcId);
+		if (npcTemplate == null)
+		{
+			_log.warning(getClass().getSimpleName() + ": Data missing in NPC table for ID: " + npcId + ".");
+			return false;
+		}
+		
+		if (npcTemplate.isType("L2SiegeGuard") || npcTemplate.isType("L2RaidBoss") || (!Config.ALLOW_CLASS_MASTERS && npcTemplate.isType("L2ClassMaster")))
+		{
+			// Don't spawn
+			return false;
+		}
+		
+		return true;
+	}
+	
+	@Override
+	protected void parseDocument()
+	{
+		NamedNodeMap attrs;
+		for (Node list = getCurrentDocument().getFirstChild(); list != null; list = list.getNextSibling())
+		{
+			if (list.getNodeName().equalsIgnoreCase("list"))
+			{
+				for (Node param = list.getFirstChild(); param != null; param = param.getNextSibling())
+				{
+					attrs = param.getAttributes();
+					if (param.getNodeName().equalsIgnoreCase("spawn"))
+					{
+						String territoryName = null;
+						
+						// Check, if spawn territory specified and exists
+						if (attrs.getNamedItem("zone") != null && ZoneManager.getInstance().getSpawnTerritory(attrs.getNamedItem("zone").getNodeValue()) != null)
+						{
+							territoryName = parseString(attrs, "zone");
+						} 
+						
+						for (Node npctag = param.getFirstChild(); npctag != null; npctag = npctag.getNextSibling())
+						{
+							attrs = npctag.getAttributes();
+							if (npctag.getNodeName().equalsIgnoreCase("npc"))
+							{
+								// mandatory
+								final int templateId = parseInt(attrs, "id");
+								// coordinates are optional, if territory is specified; mandatory otherwise
+								int x = 0;
+								int y = 0;
+								int z = 0;
+								
+								try
+								{
+									 x = parseInt(attrs, "x");
+									 y = parseInt(attrs, "y");
+									 z = parseInt(attrs, "z");
+								}
+								catch (NullPointerException npe)
+								{
+									// x, y, z  can be unspecified, if this spawn is territory based, do nothing
+								}
+								
+								if ((x == 0) && (y == 0) && (territoryName == null)) // Both coordinates and zone are unspecified
+								{
+									_log.warning("XML Spawnlist: Spawn could not be initialized, both coordinates and zone are unspecified for ID " + templateId);
+									continue;
+								}
+								
+								StatsSet spawnInfo = new StatsSet();
+								spawnInfo.set("npcTemplateid", templateId);
+								spawnInfo.set("x", x);
+								spawnInfo.set("y", y);
+								spawnInfo.set("z", z);
+								spawnInfo.set("territoryName", territoryName);
+								
+								// trying to read optional parameters
+								if (attrs.getNamedItem("heading") != null)
+								{
+									spawnInfo.set("heading", parseInt(attrs, "heading"));
+								}
+								
+								if (attrs.getNamedItem("count") != null)
+								{
+									spawnInfo.set("count", parseInt(attrs, "count"));
+								}
+								
+								if (attrs.getNamedItem("respawnDelay") != null)
+								{
+									spawnInfo.set("respawnDelay", parseInt(attrs, "respawnDelay"));
+								}
+      					
+								if (attrs.getNamedItem("respawnRandom") != null)
+								{
+									spawnInfo.set("respawnRandom", parseInt(attrs, "respawnRandom"));
+								}
+								
+								if (attrs.getNamedItem("periodOfDay") != null)
+								{
+									String period = attrs.getNamedItem("periodOfDay").getNodeValue();
+									if (period.equalsIgnoreCase("day") || period.equalsIgnoreCase("night"))
+									{
+										spawnInfo.set("periodOfDay", period.equalsIgnoreCase("day") ? 1 : 2);
+									}
+								}
+								
+								_xmlSpawnCount += addSpawn(spawnInfo);
+							}
+						}									
+					}
+				}
+			}
 		}
 	}
 	
@@ -86,52 +213,30 @@ public final class SpawnTable
 			Statement s = con.createStatement();
 			ResultSet rs = s.executeQuery(isCustom ? SELECT_CUSTOM_SPAWNS : SELECT_SPAWNS))
 		{
-			L2Spawn spawn;
-			L2NpcTemplate npcTemplate;
-			int npcId;
 			while (rs.next())
 			{
-				npcId = rs.getInt("npc_templateid");
-				npcTemplate = NpcTable.getInstance().getTemplate(npcId);
-				if (npcTemplate == null)
-				{
-					_log.warning(getClass().getSimpleName() + ": Data missing in NPC table for ID: " + npcId + ".");
-					continue;
-				}
+				StatsSet spawnInfo = new StatsSet();
+				int npcId = rs.getInt("npc_templateid");				
 				
-				if (npcTemplate.isType("L2SiegeGuard") || npcTemplate.isType("L2RaidBoss") || (!Config.ALLOW_CLASS_MASTERS && npcTemplate.isType("L2ClassMaster")))
+				// Check basic requirements first
+				if (!checkTemplate(npcId))
 				{
 					// Don't spawn
 					continue;
 				}
 				
-				spawn = new L2Spawn(npcTemplate);
-				spawn.setAmount(rs.getInt("count"));
-				spawn.setX(rs.getInt("locx"));
-				spawn.setY(rs.getInt("locy"));
-				spawn.setZ(rs.getInt("locz"));
-				spawn.setHeading(rs.getInt("heading"));
-				spawn.setRespawnDelay(rs.getInt("respawn_delay"), rs.getInt("respawn_random"));
-				spawn.setCustom(isCustom);
-				int loc_id = rs.getInt("loc_id");
-				spawn.setLocationId(loc_id);
-				
-				switch (rs.getInt("periodOfDay"))
-				{
-					case 0: // default
-						npcSpawnCount += spawn.init();
-						break;
-					case 1: // Day
-						DayNightSpawnManager.getInstance().addDayCreature(spawn);
-						npcSpawnCount++;
-						break;
-					case 2: // Night
-						DayNightSpawnManager.getInstance().addNightCreature(spawn);
-						npcSpawnCount++;
-						break;
-				}
-				
-				addSpawn(spawn);
+				spawnInfo.set("npcTemplateid", npcId);
+				spawnInfo.set("count", rs.getInt("count"));
+				spawnInfo.set("x", rs.getInt("locx"));
+				spawnInfo.set("y", rs.getInt("locy"));
+				spawnInfo.set("z", rs.getInt("locz"));
+				spawnInfo.set("heading", rs.getInt("heading"));
+				spawnInfo.set("respawnDelay", rs.getInt("respawn_delay"));
+				spawnInfo.set("respawnRandom", rs.getInt("respawn_random"));
+				spawnInfo.set("locId", rs.getInt("loc_id"));
+				spawnInfo.set("periodOfDay", rs.getInt("periodOfDay"));
+				spawnInfo.set("isCustomSpawn", isCustom);
+				npcSpawnCount += addSpawn(spawnInfo);
 			}
 		}
 		catch (Exception e)
@@ -141,6 +246,52 @@ public final class SpawnTable
 		return npcSpawnCount;
 	}
 	
+	private int addSpawn(StatsSet spawnInfo)
+	{
+		L2Spawn spawnDat;
+		int ret = 0;
+		try
+		{
+			spawnDat = new L2Spawn(NpcTable.getInstance().getTemplate(spawnInfo.getInt("npcTemplateid")));
+			spawnDat.setAmount(spawnInfo.getInt("count", 1));
+			spawnDat.setX(spawnInfo.getInt("x", 0));
+			spawnDat.setY(spawnInfo.getInt("y", 0));
+			spawnDat.setZ(spawnInfo.getInt("z", 0));
+			spawnDat.setHeading(spawnInfo.getInt("heading", -1));
+			spawnDat.setRespawnDelay(spawnInfo.getInt("respawnDelay", 0), spawnInfo.getInt("respawnRandom", 0));
+			spawnDat.setLocationId(spawnInfo.getInt("locId", 0));
+			String territoryName = spawnInfo.getString("territoryName", "");
+			spawnDat.setCustom(spawnInfo.getBoolean("isCustomSpawn", false));
+			if (!territoryName.isEmpty())
+			{
+				spawnDat.setSpawnTerritory(ZoneManager.getInstance().getSpawnTerritory(territoryName));
+			}
+			switch (spawnInfo.getInt("periodOfDay", 0))
+			{
+				case 0: // default
+					ret += spawnDat.init();
+					break;
+				case 1: // Day
+					DayNightSpawnManager.getInstance().addDayCreature(spawnDat);
+					ret = 1;
+					break;
+				case 2: // Night
+					DayNightSpawnManager.getInstance().addNightCreature(spawnDat);
+					ret = 1;
+					break;
+			}
+			
+			addSpawn(spawnDat);
+		}
+		catch (Exception e)
+		{
+			// problem with initializing spawn, go to next one
+			_log.log(Level.WARNING, "Spawn could not be initialized: " + e.getMessage(), e);
+		}
+		
+		return ret;
+	}
+	
 	public Map<Integer, Set<L2Spawn>> getSpawnTable()
 	{
 		return _spawnTable;

+ 95 - 20
L2J_Server_BETA/java/com/l2jserver/gameserver/instancemanager/ZoneManager.java

@@ -37,6 +37,7 @@ import com.l2jserver.gameserver.model.L2WorldRegion;
 import com.l2jserver.gameserver.model.actor.L2Character;
 import com.l2jserver.gameserver.model.items.instance.L2ItemInstance;
 import com.l2jserver.gameserver.model.zone.AbstractZoneSettings;
+import com.l2jserver.gameserver.model.zone.L2ZoneForm;
 import com.l2jserver.gameserver.model.zone.L2ZoneRespawn;
 import com.l2jserver.gameserver.model.zone.L2ZoneType;
 import com.l2jserver.gameserver.model.zone.form.ZoneCuboid;
@@ -45,6 +46,7 @@ import com.l2jserver.gameserver.model.zone.form.ZoneNPoly;
 import com.l2jserver.gameserver.model.zone.type.L2ArenaZone;
 import com.l2jserver.gameserver.model.zone.type.L2OlympiadStadiumZone;
 import com.l2jserver.gameserver.model.zone.type.L2RespawnZone;
+import com.l2jserver.gameserver.model.zone.type.NpcSpawnTerritory;
 
 /**
  * This class manages the zones
@@ -55,6 +57,7 @@ public final class ZoneManager extends DocumentParser
 	private static final Map<String, AbstractZoneSettings> _settings = new HashMap<>();
 	
 	private final Map<Class<? extends L2ZoneType>, Map<Integer, ? extends L2ZoneType>> _classZones = new HashMap<>();
+	private final Map<String, NpcSpawnTerritory> _spawnTerritories = new HashMap<>();
 	private int _lastDynamicId = 300000;
 	private List<L2ItemInstance> _debugItems;
 	
@@ -143,6 +146,17 @@ public final class ZoneManager extends DocumentParser
 					{
 						attrs = d.getAttributes();
 						
+						attribute = attrs.getNamedItem("type");
+						if (attribute != null)
+						{
+							zoneType = attribute.getNodeValue();
+						}
+						else
+						{
+							_log.warning("ZoneData: Missing type for zone in file: " + getCurrentFile().getName());
+							continue;
+						}
+						
 						attribute = attrs.getNamedItem("id");
 						if (attribute != null)
 						{
@@ -150,7 +164,7 @@ public final class ZoneManager extends DocumentParser
 						}
 						else
 						{
-							zoneId = _lastDynamicId++;
+							zoneId = zoneType.equalsIgnoreCase("NpcSpawnTerritory") ? 0 : _lastDynamicId++;
 						}
 						
 						attribute = attrs.getNamedItem("name");
@@ -163,29 +177,29 @@ public final class ZoneManager extends DocumentParser
 							zoneName = null;
 						}
 						
+						// Check zone name for NpcSpawnTerritory. Must exist and to be unique
+						if (zoneType.equalsIgnoreCase("NpcSpawnTerritory"))
+						{
+							if (zoneName == null)
+							{
+								_log.warning("ZoneData: Missing name for NpcSpawnTerritory in file: " + getCurrentFile().getName() + ", skipping zone");
+								continue;
+							}
+							else if (_spawnTerritories.containsKey(zoneName))
+							{
+								_log.warning("ZoneData: Name " + zoneName + " already used for another zone, check file: " + getCurrentFile().getName() + ". Skipping zone");
+								continue;
+							}
+						}
+						
 						minZ = parseInt(attrs, "minZ");
 						maxZ = parseInt(attrs, "maxZ");
 						
 						zoneType = attrs.getNamedItem("type").getNodeValue();
 						zoneShape = attrs.getNamedItem("shape").getNodeValue();
 						
-						// Create the zone
-						Class<?> newZone = null;
-						Constructor<?> zoneConstructor = null;
-						L2ZoneType temp;
-						try
-						{
-							newZone = Class.forName("com.l2jserver.gameserver.model.zone.type.L2" + zoneType);
-							zoneConstructor = newZone.getConstructor(int.class);
-							temp = (L2ZoneType) zoneConstructor.newInstance(zoneId);
-						}
-						catch (Exception e)
-						{
-							_log.warning(getClass().getSimpleName() + ": ZoneData: No such zone type: " + zoneType + " in file: " + getCurrentFile().getName());
-							continue;
-						}
-						
 						// Get the zone shape from xml
+						L2ZoneForm zoneForm = null;
 						try
 						{
 							coords = null;
@@ -221,7 +235,7 @@ public final class ZoneManager extends DocumentParser
 							{
 								if (coords.length == 2)
 								{
-									temp.setZone(new ZoneCuboid(coords[0][0], coords[1][0], coords[0][1], coords[1][1], minZ, maxZ));
+									zoneForm = new ZoneCuboid(coords[0][0], coords[1][0], coords[0][1], coords[1][1], minZ, maxZ);
 								}
 								else
 								{
@@ -241,7 +255,7 @@ public final class ZoneManager extends DocumentParser
 										aX[i] = coords[i][0];
 										aY[i] = coords[i][1];
 									}
-									temp.setZone(new ZoneNPoly(aX, aY, minZ, maxZ));
+									zoneForm = new ZoneNPoly(aX, aY, minZ, maxZ);
 								}
 								else
 								{
@@ -257,7 +271,7 @@ public final class ZoneManager extends DocumentParser
 								final int zoneRad = Integer.parseInt(attrs.getNamedItem("rad").getNodeValue());
 								if ((coords.length == 1) && (zoneRad > 0))
 								{
-									temp.setZone(new ZoneCylinder(coords[0][0], coords[0][1], minZ, maxZ, zoneRad));
+									zoneForm = new ZoneCylinder(coords[0][0], coords[0][1], minZ, maxZ, zoneRad);
 								}
 								else
 								{
@@ -265,12 +279,41 @@ public final class ZoneManager extends DocumentParser
 									continue;
 								}
 							}
+							else
+							{
+								_log.warning(getClass().getSimpleName() + ": ZoneData: Unknown shape: \"" + zoneShape + "\"  for zone: " + zoneId + " in file: " + getCurrentFile().getName());
+								continue;
+							}
 						}
 						catch (Exception e)
 						{
 							_log.log(Level.WARNING, getClass().getSimpleName() + ": ZoneData: Failed to load zone " + zoneId + " coordinates: " + e.getMessage(), e);
 						}
 						
+						// No further parameters needed, if NpcSpawnTerritory is loading
+						if (zoneType.equalsIgnoreCase("NpcSpawnTerritory"))
+						{
+							_spawnTerritories.put(zoneName, new NpcSpawnTerritory(zoneName, zoneForm));
+							continue;
+						}
+						
+						// Create the zone
+						Class<?> newZone = null;
+						Constructor<?> zoneConstructor = null;
+						L2ZoneType temp;
+						try
+						{
+							newZone = Class.forName("com.l2jserver.gameserver.model.zone.type.L2" + zoneType);
+							zoneConstructor = newZone.getConstructor(int.class);
+							temp = (L2ZoneType) zoneConstructor.newInstance(zoneId);
+							temp.setZone(zoneForm);
+						}
+						catch (Exception e)
+						{
+							_log.warning(getClass().getSimpleName() + ": ZoneData: No such zone type: " + zoneType + " in file: " + getCurrentFile().getName());
+							continue;
+						}
+						
 						// Check for additional parameters
 						for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling())
 						{
@@ -341,8 +384,11 @@ public final class ZoneManager extends DocumentParser
 	public final void load()
 	{
 		_classZones.clear();
+		_spawnTerritories.clear();
 		parseDatapackDirectory("data/zones", false);
+		parseDatapackDirectory("data/zones/npcSpawnTerritories", false);
 		_log.info(getClass().getSimpleName() + ": Loaded " + _classZones.size() + " zone classes and " + getSize() + " zones.");
+		_log.info(getClass().getSimpleName() + ": Loaded " + _spawnTerritories.size() + " NPC spawn territoriers.");
 	}
 	
 	/**
@@ -547,6 +593,35 @@ public final class ZoneManager extends DocumentParser
 		return null;
 	}
 	
+	/**
+	 * Get spawm territory by name
+	 * @param name name of territory to search
+	 * @return link to zone form
+	 */
+	public NpcSpawnTerritory getSpawnTerritory(String name)
+	{
+		return _spawnTerritories.containsKey(name) ? _spawnTerritories.get(name) : null;
+	}
+	
+	/**
+	 * Returns all spawm territories from where the object is located
+	 * @param object
+	 * @return zones
+	 */
+	public List<NpcSpawnTerritory> getSpawnTerritories(L2Object object)
+	{
+		List<NpcSpawnTerritory> temp = new ArrayList<>();
+		for (NpcSpawnTerritory territory : _spawnTerritories.values())
+		{
+			if (territory.isInsideZone(object.getX(), object.getY(), object.getZ()))
+			{
+				temp.add(territory);
+			}
+		}
+		
+		return temp;
+	}
+	
 	/**
 	 * Gets the arena.
 	 * @param character the character

+ 1 - 1
L2J_Server_BETA/java/com/l2jserver/gameserver/model/L2Object.java

@@ -738,7 +738,7 @@ public abstract class L2Object extends Point3D implements IIdentifiable, INamabl
 	 */
 	public double calculateDistance(ILocational loc, boolean includeZAxis, boolean squared)
 	{
-		return calculateDistance(loc.getX(), loc.getY(), loc.getZ(), includeZAxis, squared);
+		return calculateDistance(loc.getLocation(this).getX(), loc.getLocation(this).getY(), loc.getLocation(this).getZ(), includeZAxis, squared);
 	}
 	
 	@Override

+ 92 - 5
L2J_Server_BETA/java/com/l2jserver/gameserver/model/L2Spawn.java

@@ -20,6 +20,8 @@ package com.l2jserver.gameserver.model;
 
 import java.lang.reflect.Constructor;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -36,6 +38,7 @@ import com.l2jserver.gameserver.model.actor.L2Npc;
 import com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate;
 import com.l2jserver.gameserver.model.interfaces.IIdentifiable;
 import com.l2jserver.gameserver.model.interfaces.IPositionable;
+import com.l2jserver.gameserver.model.zone.type.NpcSpawnTerritory;
 import com.l2jserver.util.Rnd;
 
 /**
@@ -61,6 +64,8 @@ public class L2Spawn implements IPositionable, IIdentifiable
 	private int _locationId;
 	/** The Location of this NPC spawn. */
 	private Location _location = new Location(0, 0, 0);
+	/** Link to NPC spawn territory */
+	private NpcSpawnTerritory _spawnTerritory = null;
 	/** Minimum respawn delay */
 	private int _respawnMinDelay;
 	/** Maximum respawn delay */
@@ -72,8 +77,9 @@ public class L2Spawn implements IPositionable, IIdentifiable
 	private boolean _doRespawn;
 	/** If true then spawn is custom */
 	private boolean _customSpawn;
-	private L2Npc _lastSpawn;
 	private static List<SpawnListener> _spawnListeners = new FastList<>();
+	private final FastList<L2Npc> _spawnedNpcs = new FastList<>();
+	private Map<Integer, Location> _lastSpawnPoints;
 	private boolean _isNoRndWalk = false; // Is no random walk
 	
 	/** The task launching the function doSpawn() */
@@ -163,12 +169,27 @@ public class L2Spawn implements IPositionable, IIdentifiable
 		return _location;
 	}
 	
+	@Override
+	public Location getLocation(L2Object obj)
+	{
+		return ((_lastSpawnPoints == null) || (obj == null) || !_lastSpawnPoints.containsKey(obj.getObjectId())) ? _location : _lastSpawnPoints.get(obj.getObjectId());
+	}
+	
 	@Override
 	public int getX()
 	{
 		return _location.getX();
 	}
 	
+	/**
+	 * @param obj object to check
+	 * @return the X position of the last spawn point of given NPC.
+	 */
+	public int getX(L2Object obj)
+	{
+		return getLocation(obj).getX();
+	}
+	
 	/**
 	 * Set the X position of the spawn point.
 	 * @param x the x coordinate
@@ -185,6 +206,15 @@ public class L2Spawn implements IPositionable, IIdentifiable
 		return _location.getY();
 	}
 	
+	/**
+	 * @param obj object to check
+	 * @return the Y position of the last spawn point of given NPC.
+	 */
+	public int getY(L2Object obj)
+	{
+		return getLocation(obj).getY();
+	}
+	
 	/**
 	 * Set the Y position of the spawn point.
 	 * @param y the y coordinate
@@ -201,6 +231,15 @@ public class L2Spawn implements IPositionable, IIdentifiable
 		return _location.getZ();
 	}
 	
+	/**
+	 * @param obj object to check
+	 * @return the Z position of the last spawn point of given NPC.
+	 */
+	public int getZ(L2Object obj)
+	{
+		return getLocation(obj).getZ();
+	}
+	
 	/**
 	 * Set the Z position of the spawn point.
 	 * @param z the z coordinate
@@ -348,6 +387,15 @@ public class L2Spawn implements IPositionable, IIdentifiable
 		// Decrease the current number of L2NpcInstance of this L2Spawn
 		_currentCount--;
 		
+		// Remove this NPC from list of spawned
+		_spawnedNpcs.remove(oldNpc);
+		
+		// Remove spawn point for old NPC
+		if (_lastSpawnPoints != null)
+		{
+			_lastSpawnPoints.remove(oldNpc.getObjectId());
+		}
+		
 		// Check if respawn is possible to prevent multiple respawning caused by lag
 		if (_doRespawn && ((_scheduledCount + _currentCount) < _maximumCount))
 		{
@@ -487,8 +535,17 @@ public class L2Spawn implements IPositionable, IIdentifiable
 	{
 		int newlocx, newlocy, newlocz;
 		
-		// If Locx=0 and Locy=0, the L2NpcInstance must be spawned in an area defined by location
-		if ((getX() == 0) && (getY() == 0))
+		// If Locx and Locy are not defined, the L2NpcInstance must be spawned in an area defined by location or spawn territory
+		// New method
+		if (isTerritoryBased())
+		{
+			int[] p = _spawnTerritory.getRandomPoint();
+			newlocx = p[0];
+			newlocy = p[1];
+			newlocz = p[2];
+		}
+		// Old method (for backward compatibility)
+		else if ((getX() == 0) && (getY() == 0))
 		{
 			if (getLocationId() == 0)
 			{
@@ -565,7 +622,11 @@ public class L2Spawn implements IPositionable, IIdentifiable
 		
 		L2Spawn.notifyNpcSpawned(mob);
 		
-		_lastSpawn = mob;
+		_spawnedNpcs.add(mob);
+		if (_lastSpawnPoints != null)
+		{
+			_lastSpawnPoints.put(mob.getObjectId(), new Location(newlocx, newlocy, newlocz));
+		}
 		
 		if (Config.DEBUG)
 		{
@@ -646,9 +707,35 @@ public class L2Spawn implements IPositionable, IIdentifiable
 		return _respawnMinDelay != _respawnMaxDelay;
 	}
 	
+	public void setSpawnTerritory(NpcSpawnTerritory territory)
+	{
+		_spawnTerritory = territory;
+		_lastSpawnPoints = new ConcurrentHashMap<>();
+	}
+	
+	public NpcSpawnTerritory getSpawnTerritory()
+	{
+		return _spawnTerritory;
+	}
+	
+	public boolean isTerritoryBased()
+	{
+		return (_spawnTerritory != null) && (_location.getX() == 0) && (_location.getY() == 0);
+	}
+	
 	public L2Npc getLastSpawn()
 	{
-		return _lastSpawn;
+		if (!_spawnedNpcs.isEmpty())
+		{
+			return _spawnedNpcs.getLast();
+		}
+		
+		return null;
+	}
+	
+	public final FastList<L2Npc> getSpawnedNpcs()
+	{
+		return _spawnedNpcs;
 	}
 	
 	/**

+ 8 - 0
L2J_Server_BETA/java/com/l2jserver/gameserver/model/Location.java

@@ -18,6 +18,7 @@
  */
 package com.l2jserver.gameserver.model;
 
+import com.l2jserver.gameserver.model.L2Object;
 import com.l2jserver.gameserver.model.interfaces.IPositionable;
 
 /**
@@ -177,6 +178,13 @@ public class Location implements IPositionable
 		return this;
 	}
 	
+	@Override
+	public IPositionable getLocation(L2Object obj)
+	{
+		return this;
+	}
+
+
 	@Override
 	public void setLocation(Location loc)
 	{

+ 1 - 1
L2J_Server_BETA/java/com/l2jserver/gameserver/model/actor/L2Attackable.java

@@ -2285,7 +2285,7 @@ public class L2Attackable extends L2Npc
 		
 		if (hasAI() && (getSpawn() != null))
 		{
-			getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, getSpawn().getLocation());
+			getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, getSpawn().getLocation(this));
 		}
 	}
 	

+ 2 - 0
L2J_Server_BETA/java/com/l2jserver/gameserver/model/interfaces/ILocational.java

@@ -18,6 +18,7 @@
  */
 package com.l2jserver.gameserver.model.interfaces;
 
+import com.l2jserver.gameserver.model.L2Object;
 
 /**
  * Simple interface for location of object.
@@ -36,4 +37,5 @@ public interface ILocational
 	public int getInstanceId();
 	
 	public ILocational getLocation();
+	public ILocational getLocation(L2Object obj);
 }

+ 61 - 0
L2J_Server_BETA/java/com/l2jserver/gameserver/model/zone/type/NpcSpawnTerritory.java

@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2004-2013 L2J Server
+ * 
+ * This file is part of L2J Server.
+ * 
+ * L2J Server is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J Server is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.gameserver.model.zone.type;
+
+import java.util.List;
+
+import com.l2jserver.gameserver.model.zone.L2ZoneForm;
+
+/**
+ * Just dummy zone, needs only for geometry calculations
+ * @author GKR
+ */
+public class NpcSpawnTerritory
+{
+	private final String _name;
+	private final L2ZoneForm _territory;
+	@SuppressWarnings("unused")
+	private List<L2ZoneForm> _bannedTerritories; // TODO: Implement it
+	
+	public NpcSpawnTerritory(String name, L2ZoneForm territory)
+	{
+		_name = name;
+		_territory = territory;
+	}
+	
+	public String getName()
+	{
+		return _name;
+	}
+	
+	public int[] getRandomPoint()
+	{
+		return _territory.getRandomPoint();
+	}
+	
+	public boolean isInsideZone(int x, int y, int z)
+	{
+		return _territory.isInsideZone(x, y, z);
+	}
+	
+	public void visualizeZone(int z)
+	{
+		_territory.visualizeZone(z);
+	}
+}

+ 7 - 0
L2J_Server_BETA/java/com/l2jserver/gameserver/util/Point3D.java

@@ -20,6 +20,7 @@ package com.l2jserver.gameserver.util;
 
 import java.util.concurrent.atomic.AtomicInteger;
 
+import com.l2jserver.gameserver.model.L2Object;
 import com.l2jserver.gameserver.model.Location;
 import com.l2jserver.gameserver.model.interfaces.IPositionable;
 
@@ -99,6 +100,12 @@ public class Point3D implements IPositionable
 		return new Location(getX(), getY(), getZ(), getHeading(), getInstanceId());
 	}
 	
+	@Override
+	public Location getLocation(L2Object obj)
+	{
+		return getLocation();
+	}
+
 	@Override
 	public void setX(int x)
 	{