Procházet zdrojové kódy

BETA: WalkingManager impovements, random respawn interval fix.
* Support for attaching certain route to certain NPC with certain spawn coordinates in Routes.xml
* onNodeArrive quest trigger, triggering, when NPC, which is controlled by Walking Manager, arrives next node
* proper handling for situation, when monster, which is controlled by Walking Manager, is attacked by player; handling for minions of Walking Manager controlled monsters.
* AI-disabled NPC's, having aggro_range parameter, which is controlled by Walking Manager, can track aggro range enter
* receiving debug information about walk using //debug command
* random respawn interval fix - _respawnMinDelay and _respawnMaxDelay are reverted to int.

* Reported by: UnAfraid (random respawn interval)
* Reviewed by: UnAfraid, Zoey76

VlLight před 12 roky
rodič
revize
ed26f8876a

+ 3 - 3
L2J_Server_BETA/java/com/l2jserver/gameserver/instancemanager/RaidBossSpawnManager.java

@@ -188,9 +188,9 @@ public class RaidBossSpawnManager
 		{
 			boss.setRaidStatus(StatusEnum.DEAD);
 			
-			final long respawnMinDelay = (long) (boss.getSpawn().getRespawnMinDelay() * Config.RAID_MIN_RESPAWN_MULTIPLIER);
-			final long respawnMaxDelay = (long) (boss.getSpawn().getRespawnMaxDelay() * Config.RAID_MAX_RESPAWN_MULTIPLIER);
-			final long respawnDelay = Rnd.get(respawnMinDelay, respawnMaxDelay);
+			final int respawnMinDelay = (int) (boss.getSpawn().getRespawnMinDelay() * Config.RAID_MIN_RESPAWN_MULTIPLIER);
+			final int respawnMaxDelay = (int) (boss.getSpawn().getRespawnMaxDelay() * Config.RAID_MAX_RESPAWN_MULTIPLIER);
+			final int respawnDelay = Rnd.get(respawnMinDelay, respawnMaxDelay);
 			final long respawnTime = Calendar.getInstance().getTimeInMillis() + respawnDelay;
 			
 			info.set("currentHP", boss.getMaxHp());

+ 335 - 108
L2J_Server_BETA/java/com/l2jserver/gameserver/instancemanager/WalkingManager.java

@@ -16,6 +16,7 @@
  * 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.instancemanager;
 
 import java.util.ArrayList;
@@ -33,7 +34,10 @@ import com.l2jserver.gameserver.engines.DocumentParser;
 import com.l2jserver.gameserver.model.L2CharPosition;
 import com.l2jserver.gameserver.model.L2NpcWalkerNode;
 import com.l2jserver.gameserver.model.L2WalkRoute;
+import com.l2jserver.gameserver.model.Location;
 import com.l2jserver.gameserver.model.actor.L2Npc;
+import com.l2jserver.gameserver.model.actor.instance.L2MonsterInstance;
+import com.l2jserver.gameserver.model.quest.Quest;
 import com.l2jserver.gameserver.network.NpcStringId;
 import com.l2jserver.util.Rnd;
 
@@ -48,48 +52,171 @@ public class WalkingManager extends DocumentParser
 	private static final byte REPEAT_GO_FIRST = 1;
 	private static final byte REPEAT_TELE_FIRST = 2;
 	private static final byte REPEAT_RANDOM = 3;
-	
+
 	protected Map<Integer, L2WalkRoute> _routes = new HashMap<>(); // all available routes
 	private final Map<Integer, WalkInfo> _activeRoutes = new HashMap<>(); // each record represents NPC, moving by predefined route from _routes, and moving progress
-	
+	private final Map<Integer, NpcRoutesHolder> _routesToAttach = new HashMap<>(); // each record represents NPC and all available routes for it
+
+	/**
+	 * Holds depending between NPC's spawn point and route
+	 */
+	private class NpcRoutesHolder
+	{
+		private final Map<String, Integer> _correspondences;
+
+		public NpcRoutesHolder()
+		{
+			_correspondences = new HashMap<>();
+		}
+
+		/**
+		 * Add correspondence between specific route and specific spawn point
+		 * @param routeId id of route
+		 * @param loc Location of spawn point
+		 */
+		public void addRoute(int routeId, Location loc)
+		{
+			_correspondences.put(getUniqueKey(loc), routeId);
+		}
+
+		/**
+		 * @param npc
+		 * @return route id for given NPC.
+		 */
+		public int getRouteId(L2Npc npc)
+		{
+			if (npc.getSpawn() != null)
+			{
+				String key = getUniqueKey(npc.getSpawn().getSpawnLocation());
+				return _correspondences.containsKey(key) ? _correspondences.get(key) : -1;
+			}
+			
+			return -1;
+		}
+
+		/**
+		 * @param loc
+		 * @return unique text string for given Location.
+		 */
+		private String getUniqueKey(Location loc)
+		{
+			return (loc.getX() + "-" + loc.getY() + "-" + loc.getZ());
+		}
+	}
+
+	/**
+	 * Holds info about current walk progress
+	 */
 	private class WalkInfo
 	{
 		protected ScheduledFuture<?> _walkCheckTask;
 		protected boolean _blocked = false;
 		protected boolean _suspended = false;
-		protected boolean _nodeArrived = false;
+		protected boolean _stoppedByAttack = false;
 		protected int _currentNode = 0;
 		protected boolean _forward = true; // Determines first --> last or first <-- last direction
 		private final int _routeId;
-		
+		protected long _lastActionTime; // Debug field
+
 		public WalkInfo(int routeId)
 		{
 			_routeId = routeId;
 		}
-		
+
+		/**
+		 * @return id of route of this WalkInfo.
+		 */
 		protected L2WalkRoute getRoute()
 		{
 			return _routes.get(_routeId);
 		}
-		
+
+		/**
+		 * @return current node of this WalkInfo.
+		 */
 		protected L2NpcWalkerNode getCurrentNode()
 		{
 			return getRoute().getNodeList().get(_currentNode);
 		}
+
+		/**
+		 * Calculate next node for this WalkInfo and send debug message from given npc
+		 * @param npc NPC to debug message to be sent from
+		 */
+		protected void calculateNextNode(L2Npc npc)
+		{
+			// Check this first, within the bounds of random moving, we have no conception of "first" or "last" node
+			if (getRoute().getRepeatType() == REPEAT_RANDOM)
+			{
+				int newNode = _currentNode;
+
+				while (newNode == _currentNode)
+				{
+					newNode = Rnd.get(getRoute().getNodesCount());
+				}
+
+				_currentNode = newNode;
+				npc.sendDebugMessage("Route id: " + getRoute().getId() + ", next random node is " + _currentNode);
+			}
+
+			else
+			{
+				if (_forward)
+				{
+					_currentNode++;
+				}
+				else
+				{
+					_currentNode--;
+				}
+
+				if (_currentNode == getRoute().getNodesCount()) // Last node arrived
+				{
+					npc.sendDebugMessage("Route id: " + getRoute().getId() + ", last node arrived");
+
+					if (!getRoute().repeatWalk())
+					{
+						cancelMoving(npc);
+						return;
+					}
+
+					switch (getRoute().getRepeatType())
+					{
+						case REPEAT_GO_BACK:
+							_forward = false;
+							_currentNode -= 2;
+							break;
+						case REPEAT_GO_FIRST:
+							_currentNode = 0;
+							break;
+						case REPEAT_TELE_FIRST:
+							npc.teleToLocation(npc.getSpawn().getLocx(), npc.getSpawn().getLocy(), npc.getSpawn().getLocz());
+							_currentNode = 0;
+							break;
+					}
+				}
+
+				else if (_currentNode == -1) // First node arrived, when direction is first <-- last
+				{
+					_currentNode = 1;
+					_forward = true;
+				}
+			}
+		}
 	}
-	
+
 	protected WalkingManager()
 	{
 		load();
 	}
-	
+
 	@Override
 	public final void load()
 	{
 		parseDatapackFile("data/Routes.xml");
 		_log.info(getClass().getSimpleName() + ": Loaded " + _routes.size() + " walking routes.");
 	}
-	
+
 	@Override
 	protected void parseDocument()
 	{
@@ -122,7 +249,7 @@ public class WalkingManager extends DocumentParser
 				{
 					repeatType = -1;
 				}
-				
+
 				final List<L2NpcWalkerNode> list = new ArrayList<>();
 				for (Node r = d.getFirstChild(); r != null; r = r.getNextSibling())
 				{
@@ -133,7 +260,7 @@ public class WalkingManager extends DocumentParser
 						int y = parseInt(attrs, "Y");
 						int z = parseInt(attrs, "Z");
 						int delay = parseInt(attrs, "delay");
-						
+
 						String chatString = null;
 						NpcStringId npcString = null;
 						Node node = attrs.getNamedItem("string");
@@ -169,18 +296,84 @@ public class WalkingManager extends DocumentParser
 						}
 						list.add(new L2NpcWalkerNode(0, npcString, chatString, x, y, z, delay, parseBoolean(attrs, "run")));
 					}
+
+					else if (r.getNodeName().equals("target"))
+					{
+						NamedNodeMap attrs = r.getAttributes();
+						try
+						{
+							int npcId = Integer.parseInt(attrs.getNamedItem("id").getNodeValue());
+							int x = 0, y = 0, z = 0;
+
+							x = Integer.parseInt(attrs.getNamedItem("spawnX").getNodeValue());
+							y = Integer.parseInt(attrs.getNamedItem("spawnY").getNodeValue());
+							z = Integer.parseInt(attrs.getNamedItem("spawnZ").getNodeValue());
+
+							NpcRoutesHolder holder = _routesToAttach.containsKey(npcId) ? _routesToAttach.get(npcId) : new NpcRoutesHolder();
+							holder.addRoute(routeId, new Location(x, y, z));
+							_routesToAttach.put(npcId, holder);
+						}
+						catch (Exception e)
+						{
+							_log.warning("Walking Manager: Error in target definition for route ID: " + routeId);
+						}
+					}
 				}
 				L2WalkRoute newRoute = new L2WalkRoute(routeId, list, repeat, false, repeatType);
 				_routes.put(routeId, newRoute);
 			}
 		}
 	}
-	
+
+	/**
+	 * @param npc NPC to check
+	 * @return {@code true} if given NPC, or its leader is controlled by Walking Manager and moves currently.
+	 */
+	public boolean isOnWalk(L2Npc npc)
+	{
+		L2MonsterInstance monster = null;
+		
+		if (npc.isMonster())
+		{
+			if (((L2MonsterInstance) npc).getLeader() == null)
+			{
+				monster = (L2MonsterInstance) npc;
+			}
+			else
+			{
+				monster = ((L2MonsterInstance) npc).getLeader();
+			}
+		}
+
+		if (((monster != null) && !isRegistered(monster)) || !isRegistered(npc))
+		{
+			return false;
+		}
+
+		WalkInfo walk = monster != null ? _activeRoutes.get(monster.getObjectId()) : _activeRoutes.get(npc.getObjectId());
+
+		if (walk._stoppedByAttack || walk._suspended)
+		{
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * @param npc NPC to check
+	 * @return {@code true} if given NPC controlled by Walking Manager.
+	 */
 	public boolean isRegistered(L2Npc npc)
 	{
 		return _activeRoutes.containsKey(npc.getObjectId());
 	}
-	
+
+	/**
+	 * Start to move given NPC by given route
+	 * @param npc NPC to move
+	 * @param routeId id of route to move by
+	 */
 	public void startMoving(final L2Npc npc, final int routeId)
 	{
 		if (_routes.containsKey(routeId) && (npc != null) && !npc.isDead()) // check, if these route and NPC present
@@ -191,14 +384,29 @@ public class WalkingManager extends DocumentParser
 				if ((npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_ACTIVE) || (npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE))
 				{
 					WalkInfo walk = new WalkInfo(routeId);
-					// walk._lastActionTime = System.currentTimeMillis();
+
+					if (npc.isDebug())
+					{
+						walk._lastActionTime = System.currentTimeMillis();
+					}
+
 					L2NpcWalkerNode node = walk.getCurrentNode();
-					
+
+					// adjust next waypoint, if NPC spawns at first waypoint
+					if ((npc.getX() == node.getMoveX()) && (npc.getY() == node.getMoveY()))
+					{
+						walk.calculateNextNode(npc);
+						node = walk.getCurrentNode();
+						npc.sendDebugMessage("Route id " + routeId + ", spawn point is same with first waypoint, adjusted to next");
+					}
+
 					if (!npc.isInsideRadius(node.getMoveX(), node.getMoveY(), node.getMoveZ(), 3000, true, false))
 					{
+						npc.sendDebugMessage("Route id " + routeId + ", NPC is too far from starting point, walking will no start");
 						return;
 					}
-					
+
+					npc.sendDebugMessage("Starting to move at route " + routeId);
 					npc.setIsRunning(node.getRunning());
 					npc.getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new L2CharPosition(node.getMoveX(), node.getMoveY(), node.getMoveZ(), 0));
 					walk._walkCheckTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(new Runnable()
@@ -209,13 +417,14 @@ public class WalkingManager extends DocumentParser
 							startMoving(npc, routeId);
 						}
 					}, 60000, 60000); // start walk check task, for resuming walk after fight
-					
+
 					npc.getKnownList().startTrackingTask();
-					
+
 					_activeRoutes.put(npc.getObjectId(), walk); // register route
 				}
 				else
 				{
+					npc.sendDebugMessage("Trying to start move at route " + routeId + ", but cannot now, scheduled");
 					ThreadPoolManager.getInstance().scheduleGeneral(new Runnable()
 					{
 						@Override
@@ -232,66 +441,34 @@ public class WalkingManager extends DocumentParser
 				if ((npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_ACTIVE) || (npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE))
 				{
 					WalkInfo walk = _activeRoutes.get(npc.getObjectId());
-					
+
 					// Prevent call simultaneously from scheduled task and onArrived() or temporarily stop walking for resuming in future
 					if (walk._blocked || walk._suspended)
 					{
+						npc.sendDebugMessage("Trying continue to move at route " + routeId + ", but cannot now (operation is blocked)");
 						return;
 					}
-					
+
 					walk._blocked = true;
-					// Check this first, within the bounds of random moving, we have no conception of "first" or "last" node
-					if ((walk.getRoute().getRepeatType() == REPEAT_RANDOM) && walk._nodeArrived)
-					{
-						int newNode = walk._currentNode;
-						
-						while (newNode == walk._currentNode)
-						{
-							newNode = Rnd.get(walk.getRoute().getNodesCount());
-						}
-						
-						walk._currentNode = newNode;
-						walk._nodeArrived = false;
-					}
-					
-					else if (walk._currentNode == walk.getRoute().getNodesCount()) // Last node arrived
-					{
-						if (!walk.getRoute().repeatWalk())
-						{
-							cancelMoving(npc);
-							return;
-						}
-						
-						switch (walk.getRoute().getRepeatType())
-						{
-							case REPEAT_GO_BACK:
-								walk._forward = false;
-								walk._currentNode -= 2;
-								break;
-							case REPEAT_GO_FIRST:
-								walk._currentNode = 0;
-								break;
-							case REPEAT_TELE_FIRST:
-								npc.teleToLocation(npc.getSpawn().getLocx(), npc.getSpawn().getLocy(), npc.getSpawn().getLocz());
-								walk._currentNode = 0;
-						}
-					}
-					
-					else if (walk._currentNode == -1) // First node arrived, when direction is first <-- last
-					{
-						walk._currentNode = 1;
-						walk._forward = true;
-					}
-					
 					L2NpcWalkerNode node = walk.getCurrentNode();
+					npc.sendDebugMessage("Route id: " + routeId + ", continue to node " + walk._currentNode);
 					npc.setIsRunning(node.getRunning());
 					npc.getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new L2CharPosition(node.getMoveX(), node.getMoveY(), node.getMoveZ(), 0));
 					walk._blocked = false;
+					walk._stoppedByAttack = false;
+				}
+				else
+				{
+					npc.sendDebugMessage("Trying continue to move at route " + routeId + ", but cannot now (wrong AI state)");
 				}
 			}
 		}
 	}
-	
+
+	/**
+	 * Cancel NPC moving permanently
+	 * @param npc NPC to cancel
+	 */
 	public synchronized void cancelMoving(L2Npc npc)
 	{
 		if (_activeRoutes.containsKey(npc.getObjectId()))
@@ -301,7 +478,11 @@ public class WalkingManager extends DocumentParser
 			npc.getKnownList().stopTrackingTask();
 		}
 	}
-	
+
+	/**
+	 * Resumes previously stopped moving
+	 * @param npc NPC to resume
+	 */
 	public void resumeMoving(final L2Npc npc)
 	{
 		if (!_activeRoutes.containsKey(npc.getObjectId()))
@@ -311,86 +492,132 @@ public class WalkingManager extends DocumentParser
 		
 		WalkInfo walk = _activeRoutes.get(npc.getObjectId());
 		walk._suspended = false;
+		walk._stoppedByAttack = false;
 		startMoving(npc, walk.getRoute().getId());
 	}
-	
-	public void stopMoving(L2Npc npc, boolean suspend)
+
+	/**
+	 * Pause NPC moving until it will be resumed
+	 * @param npc NPC to pause moving
+	 * @param suspend {@code true} if moving was temporarily suspended for some reasons of AI-controlling script
+	 * @param stoppedByAttack {@code true} if moving was suspended because of NPC was attacked or desired to attack
+	 */
+	public void stopMoving(L2Npc npc, boolean suspend, boolean stoppedByAttack)
 	{
-		if (!_activeRoutes.containsKey(npc.getObjectId()))
+		L2MonsterInstance monster = null;
+
+		if (npc.isMonster())
+		{
+			if (((L2MonsterInstance) npc).getLeader() == null)
+			{
+				monster = (L2MonsterInstance) npc;
+			}
+			else
+			{
+				monster = ((L2MonsterInstance) npc).getLeader();
+			}
+		}
+
+		if (((monster != null) && !isRegistered(monster)) || !isRegistered(npc))
 		{
 			return;
 		}
-		
-		WalkInfo walk = _activeRoutes.get(npc.getObjectId());
+
+		WalkInfo walk = monster != null ? _activeRoutes.get(monster.getObjectId()) : _activeRoutes.get(npc.getObjectId());
+
 		walk._suspended = suspend;
-		npc.stopMove(null);
-		npc.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
+		walk._stoppedByAttack = stoppedByAttack;
+
+		if (monster != null)
+		{
+			monster.stopMove(null);
+			monster.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
+		}
+		else
+		{
+			npc.stopMove(null);
+			npc.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
+		}
 	}
-	
+
+	/**
+	 * Manage "node arriving"-related tasks: schedule move to next node; send ON_NODE_ARRIVED event to Quest script
+	 * @param npc NPC to manage
+	 */
 	public void onArrived(final L2Npc npc)
 	{
 		if (_activeRoutes.containsKey(npc.getObjectId()))
 		{
+			// Notify quest
+			if (npc.getTemplate().getEventQuests(Quest.QuestEventType.ON_NODE_ARRIVED) != null)
+			{
+				for (Quest quest : npc.getTemplate().getEventQuests(Quest.QuestEventType.ON_NODE_ARRIVED))
+				{
+					quest.notifyNodeArrived(npc);
+				}
+			}
+
 			WalkInfo walk = _activeRoutes.get(npc.getObjectId());
-			
+
 			// Opposite should not happen... but happens sometime
 			if ((walk._currentNode >= 0) && (walk._currentNode < walk.getRoute().getNodesCount()))
 			{
 				L2NpcWalkerNode node = walk.getRoute().getNodeList().get(walk._currentNode);
 				if ((node.getMoveX() == npc.getX()) && (node.getMoveY() == npc.getY()))
 				{
-					walk._nodeArrived = true;
-					if (walk.getRoute().getRepeatType() != REPEAT_RANDOM)
-					{
-						if (walk._forward)
-						{
-							walk._currentNode++;
-						}
-						else
-						{
-							walk._currentNode--;
-						}
-					}
-					
-					int delay;
-					
-					if (walk._currentNode >= walk.getRoute().getNodesCount())
-					{
-						delay = walk.getRoute().getLastNode().getDelay();
-					}
-					else if (walk._currentNode < 0)
-					{
-						delay = walk.getRoute().getNodeList().get(0).getDelay();
-					}
-					else
+					npc.sendDebugMessage("Route id: " + walk.getRoute().getId() + ", arrived to node " + walk._currentNode);
+					npc.sendDebugMessage("Done in " + ((System.currentTimeMillis() - walk._lastActionTime) / 1000) + " s.");
+					walk.calculateNextNode(npc);
+					int delay = walk.getCurrentNode().getDelay();
+					walk._blocked = true; // prevents to be ran from walk check task, if there is delay in this node.
+
+					if (npc.isDebug())
 					{
-						delay = walk.getCurrentNode().getDelay();
+						walk._lastActionTime = System.currentTimeMillis();
 					}
-					
-					walk._blocked = true; // prevents to be ran from walk check task, if there is delay in this node.
-					// walk._lastActionTime = System.currentTimeMillis();
 					ThreadPoolManager.getInstance().scheduleGeneral(new ArrivedTask(npc, walk), 100 + (delay * 1000L));
 				}
 			}
 		}
 	}
-	
+
+	/**
+	 * Manage "on death"-related tasks: permanently cancel moving of died NPC
+	 * @param npc NPC to manage
+	 */
 	public void onDeath(L2Npc npc)
 	{
 		cancelMoving(npc);
 	}
-	
+
+	/**
+	 * Manage "on spawn"-related tasks: start NPC moving, if there is route attached to its spawn point
+	 * @param npc NPC to manage
+	 */
+	public void onSpawn(L2Npc npc)
+	{
+		if (_routesToAttach.containsKey(npc.getNpcId()))
+		{
+			int routeId = _routesToAttach.get(npc.getNpcId()).getRouteId(npc);
+
+			if (routeId > 0)
+			{
+				startMoving(npc, routeId);
+			}
+		}
+	}
+
 	private class ArrivedTask implements Runnable
 	{
 		WalkInfo _walk;
 		L2Npc _npc;
-		
+
 		public ArrivedTask(L2Npc npc, WalkInfo walk)
 		{
 			_npc = npc;
 			_walk = walk;
 		}
-		
+
 		@Override
 		public void run()
 		{
@@ -398,12 +625,12 @@ public class WalkingManager extends DocumentParser
 			startMoving(_npc, _walk.getRoute().getId());
 		}
 	}
-	
+
 	public static final WalkingManager getInstance()
 	{
 		return SingletonHolder._instance;
 	}
-	
+
 	private static class SingletonHolder
 	{
 		protected static final WalkingManager _instance = new WalkingManager();

+ 9 - 9
L2J_Server_BETA/java/com/l2jserver/gameserver/model/L2Spawn.java

@@ -78,10 +78,10 @@ public class L2Spawn
 	private int _heading;
 	
 	/** Minimum respawn delay */
-	private long _respawnMinDelay;
+	private int _respawnMinDelay;
 	
 	/** Maximum respawn delay */
-	private long _respawnMaxDelay;
+	private int _respawnMaxDelay;
 	
 	private int _instanceId = 0;
 	
@@ -226,7 +226,7 @@ public class L2Spawn
 	/**
 	 * @return min respawn delay.
 	 */
-	public long getRespawnMinDelay()
+	public int getRespawnMinDelay()
 	{
 		return _respawnMinDelay;
 	}
@@ -234,7 +234,7 @@ public class L2Spawn
 	/**
 	 * @return max respawn delay.
 	 */
-	public long getRespawnMaxDelay()
+	public int getRespawnMaxDelay()
 	{
 		return _respawnMaxDelay;
 	}
@@ -261,7 +261,7 @@ public class L2Spawn
 	 * Set Minimum Respawn Delay.
 	 * @param date
 	 */
-	public void setRespawnMinDelay(long date)
+	public void setRespawnMinDelay(int date)
 	{
 		_respawnMinDelay = date;
 	}
@@ -270,7 +270,7 @@ public class L2Spawn
 	 * Set Maximum Respawn Delay.
 	 * @param date
 	 */
-	public void setRespawnMaxDelay(long date)
+	public void setRespawnMaxDelay(int date)
 	{
 		_respawnMaxDelay = date;
 	}
@@ -629,8 +629,8 @@ public class L2Spawn
 			int minDelay = delay - randomInterval; 
 			int maxDelay = delay + randomInterval;
 
-			_respawnMinDelay = Math.max(10, minDelay) * 1000L;
-			_respawnMaxDelay = Math.max(10, maxDelay) * 1000L;
+			_respawnMinDelay = Math.max(10, minDelay) * 1000;
+			_respawnMaxDelay = Math.max(10, maxDelay) * 1000;
 		}
 
 		else
@@ -645,7 +645,7 @@ public class L2Spawn
 		setRespawnDelay(delay, 0);
 	}
 
-	public long getRespawnDelay()
+	public int getRespawnDelay()
 	{
 		return (_respawnMinDelay + _respawnMaxDelay) / 2;
 	}

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

@@ -42,6 +42,7 @@ import com.l2jserver.gameserver.datatables.ItemTable;
 import com.l2jserver.gameserver.datatables.ManorData;
 import com.l2jserver.gameserver.datatables.SkillTable;
 import com.l2jserver.gameserver.instancemanager.CursedWeaponsManager;
+import com.l2jserver.gameserver.instancemanager.WalkingManager;
 import com.l2jserver.gameserver.model.L2CharPosition;
 import com.l2jserver.gameserver.model.L2CommandChannel;
 import com.l2jserver.gameserver.model.L2DropCategory;
@@ -1001,6 +1002,12 @@ public class L2Attackable extends L2Npc
 		{
 			try
 			{
+				// If monster is on walk - stop it
+				if (isWalker() && !isCoreAIDisabled() && WalkingManager.getInstance().isOnWalk(this))
+				{
+					WalkingManager.getInstance().stopMoving(this, false, true);
+				}
+
 				L2PcInstance player = attacker.getActingPlayer();
 				if (player != null)
 				{

+ 6 - 0
L2J_Server_BETA/java/com/l2jserver/gameserver/model/actor/L2Character.java

@@ -3910,6 +3910,12 @@ public abstract class L2Character extends L2Object
 		 */
 		public void detachAI()
 		{
+			// Skip character, if it is controlled by Walking Manager
+			if (L2Character.this.isWalker())
+			{
+				return;
+			}
+
 			_ai = null;
 		}
 	}

+ 10 - 2
L2J_Server_BETA/java/com/l2jserver/gameserver/model/actor/L2Npc.java

@@ -1445,6 +1445,11 @@ public class L2Npc extends L2Character
 				quest.notifySpawn(this);
 			}
 		}
+
+		if (!isTeleporting())
+		{
+			WalkingManager.getInstance().onSpawn(this);
+		}
 	}
 	
 	/**
@@ -1795,13 +1800,16 @@ public class L2Npc extends L2Character
 			player.sendPacket(new AbstractNpcInfo.NpcInfo(this, player));
 		}
 	}
-	
+
+	/**
+	 * @return {@code true} if this L2Npc is registered in WalkingManager
+	 */
 	@Override
 	public boolean isWalker()
 	{
 		return WalkingManager.getInstance().isRegistered(this);
 	}
-	
+
 	@Override
 	public boolean isChargedShot(ShotType type)
 	{

+ 9 - 0
L2J_Server_BETA/java/com/l2jserver/gameserver/model/actor/instance/L2MonsterInstance.java

@@ -238,4 +238,13 @@ public class L2MonsterInstance extends L2Attackable
 	{
 		return true;
 	}
+
+	/**
+	 * @return true if this L2MonsterInstance (or its master) is registered in WalkingManager
+	 */
+	@Override
+	public boolean isWalker()
+	{
+		return ((getLeader() == null) ? super.isWalker() : getLeader().isWalker());
+	}
 }

+ 10 - 11
L2J_Server_BETA/java/com/l2jserver/gameserver/model/actor/knownlist/AttackableKnownList.java

@@ -21,7 +21,6 @@ package com.l2jserver.gameserver.model.actor.knownlist;
 import java.util.Collection;
 
 import com.l2jserver.gameserver.ai.CtrlIntention;
-import com.l2jserver.gameserver.instancemanager.WalkingManager;
 import com.l2jserver.gameserver.model.L2Object;
 import com.l2jserver.gameserver.model.actor.L2Attackable;
 import com.l2jserver.gameserver.model.actor.L2Character;
@@ -33,7 +32,7 @@ public class AttackableKnownList extends NpcKnownList
 	{
 		super(activeChar);
 	}
-	
+
 	@Override
 	protected boolean removeKnownObject(L2Object object, boolean forget)
 	{
@@ -41,7 +40,7 @@ public class AttackableKnownList extends NpcKnownList
 		{
 			return false;
 		}
-		
+
 		// Remove the L2Object from the _aggrolist of the L2Attackable
 		if (object instanceof L2Character)
 		{
@@ -49,28 +48,28 @@ public class AttackableKnownList extends NpcKnownList
 		}
 		// Set the L2Attackable Intention to AI_INTENTION_IDLE
 		final Collection<L2PcInstance> known = getKnownPlayers().values();
-		
+
 		// FIXME: This is a temporary solution && support for Walking Manager
-		if (getActiveChar().hasAI() && ((known == null) || known.isEmpty()) && !WalkingManager.getInstance().isRegistered(getActiveChar()))
+		if (getActiveChar().hasAI() && ((known == null) || known.isEmpty()) && !getActiveChar().isWalker())
 		{
 			getActiveChar().getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE, null);
 		}
 		
 		return true;
 	}
-	
+
 	@Override
 	public L2Attackable getActiveChar()
 	{
 		return (L2Attackable) super.getActiveChar();
 	}
-	
+
 	@Override
 	public int getDistanceToForgetObject(L2Object object)
 	{
 		return (int) (getDistanceToWatchObject(object) * 1.5);
 	}
-	
+
 	@Override
 	public int getDistanceToWatchObject(L2Object object)
 	{
@@ -78,14 +77,14 @@ public class AttackableKnownList extends NpcKnownList
 		{
 			return 0;
 		}
-		
+
 		if (object.isPlayable())
 		{
 			return object.getKnownList().getDistanceToWatchObject(getActiveObject());
 		}
-		
+
 		int max = Math.max(300, Math.max(getActiveChar().getAggroRange(), Math.max(getActiveChar().getFactionRange(), getActiveChar().getEnemyRange())));
-		
+
 		return max;
 	}
 }

+ 28 - 19
L2J_Server_BETA/java/com/l2jserver/gameserver/model/actor/knownlist/NpcKnownList.java

@@ -34,24 +34,24 @@ import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
 public class NpcKnownList extends CharKnownList
 {
 	private ScheduledFuture<?> _trackingTask = null;
-	
+
 	public NpcKnownList(L2Npc activeChar)
 	{
 		super(activeChar);
 	}
-	
+
 	@Override
 	public L2Npc getActiveChar()
 	{
 		return (L2Npc) super.getActiveChar();
 	}
-	
+
 	@Override
 	public int getDistanceToForgetObject(L2Object object)
 	{
 		return 2 * getDistanceToWatchObject(object);
 	}
-	
+
 	@Override
 	public int getDistanceToWatchObject(L2Object object)
 	{
@@ -59,21 +59,21 @@ public class NpcKnownList extends CharKnownList
 		{
 			return 0;
 		}
-		
+
 		if (object instanceof L2FestivalGuideInstance)
 		{
 			return 4000;
 		}
-		
+
 		if (object.isPlayable())
 		{
 			return 1500;
 		}
-		
+
 		return 500;
 	}
-	
-	// L2Master mod - support for Walking monsters aggro
+
+	// Support for Walking monsters aggro
 	public void startTrackingTask()
 	{
 		if ((_trackingTask == null) && (getActiveChar().getAggroRange() > 0))
@@ -81,8 +81,8 @@ public class NpcKnownList extends CharKnownList
 			_trackingTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(new TrackingTask(), 2000, 2000);
 		}
 	}
-	
-	// L2Master mod - support for Walking monsters aggro
+
+	// Support for Walking monsters aggro
 	public void stopTrackingTask()
 	{
 		if (_trackingTask != null)
@@ -91,15 +91,15 @@ public class NpcKnownList extends CharKnownList
 			_trackingTask = null;
 		}
 	}
-	
-	// L2Master mod - support for Walking monsters aggro
+
+	// Support for Walking monsters aggro
 	private class TrackingTask implements Runnable
 	{
 		public TrackingTask()
 		{
 			//
 		}
-		
+
 		@Override
 		public void run()
 		{
@@ -113,12 +113,21 @@ public class NpcKnownList extends CharKnownList
 					{
 						for (L2PcInstance pl : players)
 						{
-							if (pl.isInsideRadius(monster, monster.getAggroRange(), true, false) && !pl.isDead() && !pl.isInvul())
+							if (!pl.isDead() && !pl.isInvul() && pl.isInsideRadius(monster, monster.getAggroRange(), true, false))
 							{
-								WalkingManager.getInstance().stopMoving(getActiveChar(), false);
-								monster.addDamageHate(pl, 0, 100);
-								monster.getAI().setIntention(CtrlIntention.AI_INTENTION_ATTACK, pl, null);
-								break;
+								// Send aggroRangeEnter
+								if (monster.getHating(pl) == 0)
+								{
+									monster.addDamageHate(pl, 0, 0);
+								}
+
+								// Skip attack for other targets, if one is already choosen for attack
+								if ((monster.getAI().getIntention() != CtrlIntention.AI_INTENTION_ATTACK) && !monster.isCoreAIDisabled())
+								{
+									WalkingManager.getInstance().stopMoving(getActiveChar(), false, true);
+									monster.addDamageHate(pl, 0, 100);
+									monster.getAI().setIntention(CtrlIntention.AI_INTENTION_ATTACK, pl, null);
+								}
 							}
 						}
 					}

+ 55 - 3
L2J_Server_BETA/java/com/l2jserver/gameserver/model/quest/Quest.java

@@ -352,7 +352,8 @@ public class Quest extends ManagedScript
 		ON_TRAP_ACTION(true), // on zone exit
 		ON_ITEM_USE(true),
 		ON_EVENT_RECEIVED(true), // onEventReceived action, triggered when NPC recieving an event, sent by other NPC
-		ON_MOVE_FINISHED(true); // onMoveFinished action, triggered when NPC stops after moving
+		ON_MOVE_FINISHED(true), // onMoveFinished action, triggered when NPC stops after moving
+		ON_NODE_ARRIVED(true); // onNodeArrived action, triggered when NPC, controlled by Walking Manager, arrives to next node
 		
 		// control whether this event type is allowed for the same npc template in multiple quests
 		// or if the npc must be registered in at most one quest for the specified event
@@ -1193,6 +1194,21 @@ public class Quest extends ManagedScript
 		}
 	}
 	
+	/**
+	 * @param npc
+	 */
+	public final void notifyNodeArrived(L2Npc npc)
+	{
+		try
+		{
+			onNodeArrived(npc);
+		}
+		catch (Exception e)
+		{
+			_log.log(Level.WARNING, "Exception on onNodeArrived() in notifyNodeArrived(): " + e.getMessage(), e);
+		}
+	}
+
 	// These are methods that java calls to invoke scripts.
 	
 	/**
@@ -1540,7 +1556,17 @@ public class Quest extends ManagedScript
 	{
 		return null;
 	}
-	
+
+	/**
+	 * This function is called whenever a walker NPC (controlled by WalkingManager) arrive a walking node
+	 * @param npc registered NPC
+	 * @return
+	 */
+	public String onNodeArrived(L2Npc npc)
+	{
+		return null;
+	}
+
 	/**
 	 * Show an error message to the specified player.
 	 * @param player the player to whom to send the error (must be a GM)
@@ -2410,7 +2436,33 @@ public class Quest extends ManagedScript
 		}
 		return value;
 	}
-	
+
+	/**
+	 * Register addNodeArrived trigger for NPC
+	 * @param npcId id of NPC to register
+	 * @return
+	 */
+	public L2NpcTemplate addNodeArrivedId(int npcId)
+	{
+		return addEventId(npcId, QuestEventType.ON_NODE_ARRIVED);
+	}
+
+	/**
+	 * Register addNodeArrived trigger for NPC
+	 * @param npcIds id of NPC to register
+	 * @return
+	 */
+	public L2NpcTemplate[] addNodeArrivedId(int... npcIds)
+	{
+		L2NpcTemplate[] value = new L2NpcTemplate[npcIds.length];
+		int i = 0;
+		for (int npcId : npcIds)
+		{
+			value[i++] = addEventId(npcId, QuestEventType.ON_NODE_ARRIVED);
+		}
+		return value;
+	}
+
 	/**
 	 * Use this method to get a random party member from a player's party.<br>
 	 * Useful when distributing rewards after killing an NPC.