WalkingManager.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. /*
  2. * Copyright (C) 2004-2013 L2J Server
  3. *
  4. * This file is part of L2J Server.
  5. *
  6. * L2J Server is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * L2J Server is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.l2jserver.gameserver.instancemanager;
  20. import java.util.ArrayList;
  21. import java.util.HashMap;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.concurrent.ScheduledFuture;
  25. import org.w3c.dom.NamedNodeMap;
  26. import org.w3c.dom.Node;
  27. import com.l2jserver.gameserver.ThreadPoolManager;
  28. import com.l2jserver.gameserver.ai.CtrlIntention;
  29. import com.l2jserver.gameserver.engines.DocumentParser;
  30. import com.l2jserver.gameserver.model.L2CharPosition;
  31. import com.l2jserver.gameserver.model.L2NpcWalkerNode;
  32. import com.l2jserver.gameserver.model.L2WalkRoute;
  33. import com.l2jserver.gameserver.model.Location;
  34. import com.l2jserver.gameserver.model.actor.L2Npc;
  35. import com.l2jserver.gameserver.model.actor.instance.L2MonsterInstance;
  36. import com.l2jserver.gameserver.model.quest.Quest;
  37. import com.l2jserver.gameserver.network.NpcStringId;
  38. import com.l2jserver.util.Rnd;
  39. /**
  40. * This class manages walking monsters.
  41. * @author GKR
  42. */
  43. public class WalkingManager extends DocumentParser
  44. {
  45. // Repeat style: 0 - go back, 1 - go to first point (circle style), 2 - teleport to first point (conveyor style), 3 - random walking between points.
  46. private static final byte REPEAT_GO_BACK = 0;
  47. private static final byte REPEAT_GO_FIRST = 1;
  48. private static final byte REPEAT_TELE_FIRST = 2;
  49. private static final byte REPEAT_RANDOM = 3;
  50. protected Map<Integer, L2WalkRoute> _routes = new HashMap<>(); // all available routes
  51. private final Map<Integer, WalkInfo> _activeRoutes = new HashMap<>(); // each record represents NPC, moving by predefined route from _routes, and moving progress
  52. private final Map<Integer, NpcRoutesHolder> _routesToAttach = new HashMap<>(); // each record represents NPC and all available routes for it
  53. /**
  54. * Holds depending between NPC's spawn point and route
  55. */
  56. private class NpcRoutesHolder
  57. {
  58. private final Map<String, Integer> _correspondences;
  59. public NpcRoutesHolder()
  60. {
  61. _correspondences = new HashMap<>();
  62. }
  63. /**
  64. * Add correspondence between specific route and specific spawn point
  65. * @param routeId id of route
  66. * @param loc Location of spawn point
  67. */
  68. public void addRoute(int routeId, Location loc)
  69. {
  70. _correspondences.put(getUniqueKey(loc), routeId);
  71. }
  72. /**
  73. * @param npc
  74. * @return route id for given NPC.
  75. */
  76. public int getRouteId(L2Npc npc)
  77. {
  78. if (npc.getSpawn() != null)
  79. {
  80. String key = getUniqueKey(npc.getSpawn().getSpawnLocation());
  81. return _correspondences.containsKey(key) ? _correspondences.get(key) : -1;
  82. }
  83. return -1;
  84. }
  85. /**
  86. * @param loc
  87. * @return unique text string for given Location.
  88. */
  89. private String getUniqueKey(Location loc)
  90. {
  91. return (loc.getX() + "-" + loc.getY() + "-" + loc.getZ());
  92. }
  93. }
  94. /**
  95. * Holds info about current walk progress
  96. */
  97. private class WalkInfo
  98. {
  99. protected ScheduledFuture<?> _walkCheckTask;
  100. protected boolean _blocked = false;
  101. protected boolean _suspended = false;
  102. protected boolean _stoppedByAttack = false;
  103. protected int _currentNode = 0;
  104. protected boolean _forward = true; // Determines first --> last or first <-- last direction
  105. private final int _routeId;
  106. protected long _lastActionTime; // Debug field
  107. public WalkInfo(int routeId)
  108. {
  109. _routeId = routeId;
  110. }
  111. /**
  112. * @return id of route of this WalkInfo.
  113. */
  114. protected L2WalkRoute getRoute()
  115. {
  116. return _routes.get(_routeId);
  117. }
  118. /**
  119. * @return current node of this WalkInfo.
  120. */
  121. protected L2NpcWalkerNode getCurrentNode()
  122. {
  123. return getRoute().getNodeList().get(_currentNode);
  124. }
  125. /**
  126. * Calculate next node for this WalkInfo and send debug message from given npc
  127. * @param npc NPC to debug message to be sent from
  128. */
  129. protected void calculateNextNode(L2Npc npc)
  130. {
  131. // Check this first, within the bounds of random moving, we have no conception of "first" or "last" node
  132. if (getRoute().getRepeatType() == REPEAT_RANDOM)
  133. {
  134. int newNode = _currentNode;
  135. while (newNode == _currentNode)
  136. {
  137. newNode = Rnd.get(getRoute().getNodesCount());
  138. }
  139. _currentNode = newNode;
  140. npc.sendDebugMessage("Route id: " + getRoute().getId() + ", next random node is " + _currentNode);
  141. }
  142. else
  143. {
  144. if (_forward)
  145. {
  146. _currentNode++;
  147. }
  148. else
  149. {
  150. _currentNode--;
  151. }
  152. if (_currentNode == getRoute().getNodesCount()) // Last node arrived
  153. {
  154. npc.sendDebugMessage("Route id: " + getRoute().getId() + ", last node arrived");
  155. if (!getRoute().repeatWalk())
  156. {
  157. cancelMoving(npc);
  158. return;
  159. }
  160. switch (getRoute().getRepeatType())
  161. {
  162. case REPEAT_GO_BACK:
  163. _forward = false;
  164. _currentNode -= 2;
  165. break;
  166. case REPEAT_GO_FIRST:
  167. _currentNode = 0;
  168. break;
  169. case REPEAT_TELE_FIRST:
  170. npc.teleToLocation(npc.getSpawn().getLocx(), npc.getSpawn().getLocy(), npc.getSpawn().getLocz());
  171. _currentNode = 0;
  172. break;
  173. }
  174. }
  175. else if (_currentNode == -1) // First node arrived, when direction is first <-- last
  176. {
  177. _currentNode = 1;
  178. _forward = true;
  179. }
  180. }
  181. }
  182. }
  183. protected WalkingManager()
  184. {
  185. load();
  186. }
  187. @Override
  188. public final void load()
  189. {
  190. parseDatapackFile("data/Routes.xml");
  191. _log.info(getClass().getSimpleName() + ": Loaded " + _routes.size() + " walking routes.");
  192. }
  193. @Override
  194. protected void parseDocument()
  195. {
  196. Node n = getCurrentDocument().getFirstChild();
  197. for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
  198. {
  199. if (d.getNodeName().equals("route"))
  200. {
  201. final Integer routeId = parseInteger(d.getAttributes(), "id");
  202. boolean repeat = parseBoolean(d.getAttributes(), "repeat");
  203. String repeatStyle = d.getAttributes().getNamedItem("repeatStyle").getNodeValue();
  204. byte repeatType;
  205. if (repeatStyle.equalsIgnoreCase("back"))
  206. {
  207. repeatType = REPEAT_GO_BACK;
  208. }
  209. else if (repeatStyle.equalsIgnoreCase("cycle"))
  210. {
  211. repeatType = REPEAT_GO_FIRST;
  212. }
  213. else if (repeatStyle.equalsIgnoreCase("conveyor"))
  214. {
  215. repeatType = REPEAT_TELE_FIRST;
  216. }
  217. else if (repeatStyle.equalsIgnoreCase("random"))
  218. {
  219. repeatType = REPEAT_RANDOM;
  220. }
  221. else
  222. {
  223. repeatType = -1;
  224. }
  225. final List<L2NpcWalkerNode> list = new ArrayList<>();
  226. for (Node r = d.getFirstChild(); r != null; r = r.getNextSibling())
  227. {
  228. if (r.getNodeName().equals("point"))
  229. {
  230. NamedNodeMap attrs = r.getAttributes();
  231. int x = parseInt(attrs, "X");
  232. int y = parseInt(attrs, "Y");
  233. int z = parseInt(attrs, "Z");
  234. int delay = parseInt(attrs, "delay");
  235. String chatString = null;
  236. NpcStringId npcString = null;
  237. Node node = attrs.getNamedItem("string");
  238. if (node != null)
  239. {
  240. chatString = node.getNodeValue();
  241. }
  242. else
  243. {
  244. node = attrs.getNamedItem("npcString");
  245. if (node != null)
  246. {
  247. npcString = NpcStringId.getNpcStringId(node.getNodeValue());
  248. if (npcString == null)
  249. {
  250. _log.warning(getClass().getSimpleName() + ": Unknown npcstring '" + node.getNodeValue() + ".");
  251. continue;
  252. }
  253. }
  254. else
  255. {
  256. node = attrs.getNamedItem("npcStringId");
  257. if (node != null)
  258. {
  259. npcString = NpcStringId.getNpcStringId(Integer.parseInt(node.getNodeValue()));
  260. if (npcString == null)
  261. {
  262. _log.warning(getClass().getSimpleName() + ": Unknown npcstring '" + node.getNodeValue() + ".");
  263. continue;
  264. }
  265. }
  266. }
  267. }
  268. list.add(new L2NpcWalkerNode(0, npcString, chatString, x, y, z, delay, parseBoolean(attrs, "run")));
  269. }
  270. else if (r.getNodeName().equals("target"))
  271. {
  272. NamedNodeMap attrs = r.getAttributes();
  273. try
  274. {
  275. int npcId = Integer.parseInt(attrs.getNamedItem("id").getNodeValue());
  276. int x = 0, y = 0, z = 0;
  277. x = Integer.parseInt(attrs.getNamedItem("spawnX").getNodeValue());
  278. y = Integer.parseInt(attrs.getNamedItem("spawnY").getNodeValue());
  279. z = Integer.parseInt(attrs.getNamedItem("spawnZ").getNodeValue());
  280. NpcRoutesHolder holder = _routesToAttach.containsKey(npcId) ? _routesToAttach.get(npcId) : new NpcRoutesHolder();
  281. holder.addRoute(routeId, new Location(x, y, z));
  282. _routesToAttach.put(npcId, holder);
  283. }
  284. catch (Exception e)
  285. {
  286. _log.warning("Walking Manager: Error in target definition for route ID: " + routeId);
  287. }
  288. }
  289. }
  290. L2WalkRoute newRoute = new L2WalkRoute(routeId, list, repeat, false, repeatType);
  291. _routes.put(routeId, newRoute);
  292. }
  293. }
  294. }
  295. /**
  296. * @param npc NPC to check
  297. * @return {@code true} if given NPC, or its leader is controlled by Walking Manager and moves currently.
  298. */
  299. public boolean isOnWalk(L2Npc npc)
  300. {
  301. L2MonsterInstance monster = null;
  302. if (npc.isMonster())
  303. {
  304. if (((L2MonsterInstance) npc).getLeader() == null)
  305. {
  306. monster = (L2MonsterInstance) npc;
  307. }
  308. else
  309. {
  310. monster = ((L2MonsterInstance) npc).getLeader();
  311. }
  312. }
  313. if (((monster != null) && !isRegistered(monster)) || !isRegistered(npc))
  314. {
  315. return false;
  316. }
  317. WalkInfo walk = monster != null ? _activeRoutes.get(monster.getObjectId()) : _activeRoutes.get(npc.getObjectId());
  318. if (walk._stoppedByAttack || walk._suspended)
  319. {
  320. return false;
  321. }
  322. return true;
  323. }
  324. /**
  325. * @param npc NPC to check
  326. * @return {@code true} if given NPC controlled by Walking Manager.
  327. */
  328. public boolean isRegistered(L2Npc npc)
  329. {
  330. return _activeRoutes.containsKey(npc.getObjectId());
  331. }
  332. /**
  333. * Start to move given NPC by given route
  334. * @param npc NPC to move
  335. * @param routeId id of route to move by
  336. */
  337. public void startMoving(final L2Npc npc, final int routeId)
  338. {
  339. if (_routes.containsKey(routeId) && (npc != null) && !npc.isDead()) // check, if these route and NPC present
  340. {
  341. if (!_activeRoutes.containsKey(npc.getObjectId())) // new walk task
  342. {
  343. // only if not already moved / not engaged in battle... should not happens if called on spawn
  344. if ((npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_ACTIVE) || (npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE))
  345. {
  346. WalkInfo walk = new WalkInfo(routeId);
  347. if (npc.isDebug())
  348. {
  349. walk._lastActionTime = System.currentTimeMillis();
  350. }
  351. L2NpcWalkerNode node = walk.getCurrentNode();
  352. // adjust next waypoint, if NPC spawns at first waypoint
  353. if ((npc.getX() == node.getMoveX()) && (npc.getY() == node.getMoveY()))
  354. {
  355. walk.calculateNextNode(npc);
  356. node = walk.getCurrentNode();
  357. npc.sendDebugMessage("Route id " + routeId + ", spawn point is same with first waypoint, adjusted to next");
  358. }
  359. if (!npc.isInsideRadius(node.getMoveX(), node.getMoveY(), node.getMoveZ(), 3000, true, false))
  360. {
  361. npc.sendDebugMessage("Route id " + routeId + ", NPC is too far from starting point, walking will no start");
  362. return;
  363. }
  364. npc.sendDebugMessage("Starting to move at route " + routeId);
  365. npc.setIsRunning(node.getRunning());
  366. npc.getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new L2CharPosition(node.getMoveX(), node.getMoveY(), node.getMoveZ(), 0));
  367. walk._walkCheckTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(new Runnable()
  368. {
  369. @Override
  370. public void run()
  371. {
  372. startMoving(npc, routeId);
  373. }
  374. }, 60000, 60000); // start walk check task, for resuming walk after fight
  375. npc.getKnownList().startTrackingTask();
  376. _activeRoutes.put(npc.getObjectId(), walk); // register route
  377. }
  378. else
  379. {
  380. npc.sendDebugMessage("Trying to start move at route " + routeId + ", but cannot now, scheduled");
  381. ThreadPoolManager.getInstance().scheduleGeneral(new Runnable()
  382. {
  383. @Override
  384. public void run()
  385. {
  386. startMoving(npc, routeId);
  387. }
  388. }, 60000);
  389. }
  390. }
  391. else
  392. // walk was stopped due to some reason (arrived to node, script action, fight or something else), resume it
  393. {
  394. if ((npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_ACTIVE) || (npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE))
  395. {
  396. WalkInfo walk = _activeRoutes.get(npc.getObjectId());
  397. // Prevent call simultaneously from scheduled task and onArrived() or temporarily stop walking for resuming in future
  398. if (walk._blocked || walk._suspended)
  399. {
  400. npc.sendDebugMessage("Trying continue to move at route " + routeId + ", but cannot now (operation is blocked)");
  401. return;
  402. }
  403. walk._blocked = true;
  404. L2NpcWalkerNode node = walk.getCurrentNode();
  405. npc.sendDebugMessage("Route id: " + routeId + ", continue to node " + walk._currentNode);
  406. npc.setIsRunning(node.getRunning());
  407. npc.getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new L2CharPosition(node.getMoveX(), node.getMoveY(), node.getMoveZ(), 0));
  408. walk._blocked = false;
  409. walk._stoppedByAttack = false;
  410. }
  411. else
  412. {
  413. npc.sendDebugMessage("Trying continue to move at route " + routeId + ", but cannot now (wrong AI state)");
  414. }
  415. }
  416. }
  417. }
  418. /**
  419. * Cancel NPC moving permanently
  420. * @param npc NPC to cancel
  421. */
  422. public synchronized void cancelMoving(L2Npc npc)
  423. {
  424. if (_activeRoutes.containsKey(npc.getObjectId()))
  425. {
  426. final WalkInfo walk = _activeRoutes.remove(npc.getObjectId());
  427. walk._walkCheckTask.cancel(true);
  428. npc.getKnownList().stopTrackingTask();
  429. }
  430. }
  431. /**
  432. * Resumes previously stopped moving
  433. * @param npc NPC to resume
  434. */
  435. public void resumeMoving(final L2Npc npc)
  436. {
  437. if (!_activeRoutes.containsKey(npc.getObjectId()))
  438. {
  439. return;
  440. }
  441. WalkInfo walk = _activeRoutes.get(npc.getObjectId());
  442. walk._suspended = false;
  443. walk._stoppedByAttack = false;
  444. startMoving(npc, walk.getRoute().getId());
  445. }
  446. /**
  447. * Pause NPC moving until it will be resumed
  448. * @param npc NPC to pause moving
  449. * @param suspend {@code true} if moving was temporarily suspended for some reasons of AI-controlling script
  450. * @param stoppedByAttack {@code true} if moving was suspended because of NPC was attacked or desired to attack
  451. */
  452. public void stopMoving(L2Npc npc, boolean suspend, boolean stoppedByAttack)
  453. {
  454. L2MonsterInstance monster = null;
  455. if (npc.isMonster())
  456. {
  457. if (((L2MonsterInstance) npc).getLeader() == null)
  458. {
  459. monster = (L2MonsterInstance) npc;
  460. }
  461. else
  462. {
  463. monster = ((L2MonsterInstance) npc).getLeader();
  464. }
  465. }
  466. if (((monster != null) && !isRegistered(monster)) || !isRegistered(npc))
  467. {
  468. return;
  469. }
  470. WalkInfo walk = monster != null ? _activeRoutes.get(monster.getObjectId()) : _activeRoutes.get(npc.getObjectId());
  471. walk._suspended = suspend;
  472. walk._stoppedByAttack = stoppedByAttack;
  473. if (monster != null)
  474. {
  475. monster.stopMove(null);
  476. monster.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
  477. }
  478. else
  479. {
  480. npc.stopMove(null);
  481. npc.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
  482. }
  483. }
  484. /**
  485. * Manage "node arriving"-related tasks: schedule move to next node; send ON_NODE_ARRIVED event to Quest script
  486. * @param npc NPC to manage
  487. */
  488. public void onArrived(final L2Npc npc)
  489. {
  490. if (_activeRoutes.containsKey(npc.getObjectId()))
  491. {
  492. // Notify quest
  493. if (npc.getTemplate().getEventQuests(Quest.QuestEventType.ON_NODE_ARRIVED) != null)
  494. {
  495. for (Quest quest : npc.getTemplate().getEventQuests(Quest.QuestEventType.ON_NODE_ARRIVED))
  496. {
  497. quest.notifyNodeArrived(npc);
  498. }
  499. }
  500. WalkInfo walk = _activeRoutes.get(npc.getObjectId());
  501. // Opposite should not happen... but happens sometime
  502. if ((walk._currentNode >= 0) && (walk._currentNode < walk.getRoute().getNodesCount()))
  503. {
  504. L2NpcWalkerNode node = walk.getRoute().getNodeList().get(walk._currentNode);
  505. if ((node.getMoveX() == npc.getX()) && (node.getMoveY() == npc.getY()))
  506. {
  507. npc.sendDebugMessage("Route id: " + walk.getRoute().getId() + ", arrived to node " + walk._currentNode);
  508. npc.sendDebugMessage("Done in " + ((System.currentTimeMillis() - walk._lastActionTime) / 1000) + " s.");
  509. walk.calculateNextNode(npc);
  510. int delay = walk.getCurrentNode().getDelay();
  511. walk._blocked = true; // prevents to be ran from walk check task, if there is delay in this node.
  512. if (npc.isDebug())
  513. {
  514. walk._lastActionTime = System.currentTimeMillis();
  515. }
  516. ThreadPoolManager.getInstance().scheduleGeneral(new ArrivedTask(npc, walk), 100 + (delay * 1000L));
  517. }
  518. }
  519. }
  520. }
  521. /**
  522. * Manage "on death"-related tasks: permanently cancel moving of died NPC
  523. * @param npc NPC to manage
  524. */
  525. public void onDeath(L2Npc npc)
  526. {
  527. cancelMoving(npc);
  528. }
  529. /**
  530. * Manage "on spawn"-related tasks: start NPC moving, if there is route attached to its spawn point
  531. * @param npc NPC to manage
  532. */
  533. public void onSpawn(L2Npc npc)
  534. {
  535. if (_routesToAttach.containsKey(npc.getNpcId()))
  536. {
  537. int routeId = _routesToAttach.get(npc.getNpcId()).getRouteId(npc);
  538. if (routeId > 0)
  539. {
  540. startMoving(npc, routeId);
  541. }
  542. }
  543. }
  544. private class ArrivedTask implements Runnable
  545. {
  546. WalkInfo _walk;
  547. L2Npc _npc;
  548. public ArrivedTask(L2Npc npc, WalkInfo walk)
  549. {
  550. _npc = npc;
  551. _walk = walk;
  552. }
  553. @Override
  554. public void run()
  555. {
  556. _walk._blocked = false;
  557. startMoving(_npc, _walk.getRoute().getId());
  558. }
  559. }
  560. public static final WalkingManager getInstance()
  561. {
  562. return SingletonHolder._instance;
  563. }
  564. private static class SingletonHolder
  565. {
  566. protected static final WalkingManager _instance = new WalkingManager();
  567. }
  568. }