WalkingManager.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. /*
  2. * This program is free software: you can redistribute it and/or modify it under
  3. * the terms of the GNU General Public License as published by the Free Software
  4. * Foundation, either version 3 of the License, or (at your option) any later
  5. * version.
  6. *
  7. * This program is distributed in the hope that it will be useful, but WITHOUT
  8. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  9. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  10. * details.
  11. *
  12. * You should have received a copy of the GNU General Public License along with
  13. * this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. package com.l2jserver.gameserver.instancemanager;
  16. import java.util.ArrayList;
  17. import java.util.HashMap;
  18. import java.util.List;
  19. import java.util.Map;
  20. import java.util.concurrent.ScheduledFuture;
  21. import org.w3c.dom.NamedNodeMap;
  22. import org.w3c.dom.Node;
  23. import com.l2jserver.gameserver.ThreadPoolManager;
  24. import com.l2jserver.gameserver.ai.CtrlIntention;
  25. import com.l2jserver.gameserver.engines.DocumentParser;
  26. import com.l2jserver.gameserver.model.L2CharPosition;
  27. import com.l2jserver.gameserver.model.L2NpcWalkerNode;
  28. import com.l2jserver.gameserver.model.L2WalkRoute;
  29. import com.l2jserver.gameserver.model.actor.L2Npc;
  30. import com.l2jserver.gameserver.network.NpcStringId;
  31. import com.l2jserver.util.Rnd;
  32. /**
  33. * This class manages walking monsters.
  34. * @author GKR
  35. */
  36. public class WalkingManager extends DocumentParser
  37. {
  38. // Repeat style: 0 - go back, 1 - go to first point (circle style), 2 - teleport to first point (conveyor style), 3 - random walking between points.
  39. private static final byte REPEAT_GO_BACK = 0;
  40. private static final byte REPEAT_GO_FIRST = 1;
  41. private static final byte REPEAT_TELE_FIRST = 2;
  42. private static final byte REPEAT_RANDOM = 3;
  43. protected Map<Integer, L2WalkRoute> _routes = new HashMap<>(); // all available routes
  44. private final Map<Integer, WalkInfo> _activeRoutes = new HashMap<>(); // each record represents NPC, moving by predefined route from _routes, and moving progress
  45. private class WalkInfo
  46. {
  47. protected ScheduledFuture<?> _walkCheckTask;
  48. protected boolean _blocked = false;
  49. protected boolean _suspended = false;
  50. protected boolean _nodeArrived = false;
  51. protected int _currentNode = 0;
  52. protected boolean _forward = true; // Determines first --> last or first <-- last direction
  53. private final int _routeId;
  54. public WalkInfo(int routeId)
  55. {
  56. _routeId = routeId;
  57. }
  58. protected L2WalkRoute getRoute()
  59. {
  60. return _routes.get(_routeId);
  61. }
  62. protected L2NpcWalkerNode getCurrentNode()
  63. {
  64. return getRoute().getNodeList().get(_currentNode);
  65. }
  66. }
  67. protected WalkingManager()
  68. {
  69. load();
  70. }
  71. @Override
  72. public final void load()
  73. {
  74. parseDatapackFile("data/Routes.xml");
  75. _log.info(getClass().getSimpleName() + ": Loaded " + _routes.size() + " walking routes.");
  76. }
  77. @Override
  78. protected void parseDocument()
  79. {
  80. Node n = getCurrentDocument().getFirstChild();
  81. for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
  82. {
  83. if (d.getNodeName().equals("route"))
  84. {
  85. final Integer routeId = parseInteger(d.getAttributes(), "id");
  86. boolean repeat = parseBoolean(d.getAttributes(), "repeat");
  87. String repeatStyle = d.getAttributes().getNamedItem("repeatStyle").getNodeValue();
  88. byte repeatType;
  89. if (repeatStyle.equalsIgnoreCase("back"))
  90. {
  91. repeatType = REPEAT_GO_BACK;
  92. }
  93. else if (repeatStyle.equalsIgnoreCase("cycle"))
  94. {
  95. repeatType = REPEAT_GO_FIRST;
  96. }
  97. else if (repeatStyle.equalsIgnoreCase("conveyor"))
  98. {
  99. repeatType = REPEAT_TELE_FIRST;
  100. }
  101. else if (repeatStyle.equalsIgnoreCase("random"))
  102. {
  103. repeatType = REPEAT_RANDOM;
  104. }
  105. else
  106. {
  107. repeatType = -1;
  108. }
  109. final List<L2NpcWalkerNode> list = new ArrayList<>();
  110. for (Node r = d.getFirstChild(); r != null; r = r.getNextSibling())
  111. {
  112. if (r.getNodeName().equals("point"))
  113. {
  114. NamedNodeMap attrs = r.getAttributes();
  115. int x = parseInt(attrs, "X");
  116. int y = parseInt(attrs, "Y");
  117. int z = parseInt(attrs, "Z");
  118. int delay = parseInt(attrs, "delay");
  119. String chatString = null;
  120. NpcStringId npcString = null;
  121. Node node = attrs.getNamedItem("string");
  122. if (node != null)
  123. {
  124. chatString = node.getNodeValue();
  125. }
  126. else
  127. {
  128. node = attrs.getNamedItem("npcString");
  129. if (node != null)
  130. {
  131. npcString = NpcStringId.getNpcStringId(node.getNodeValue());
  132. if (npcString == null)
  133. {
  134. _log.warning(getClass().getSimpleName() + ": Unknown npcstring '" + node.getNodeValue() + ".");
  135. continue;
  136. }
  137. }
  138. else
  139. {
  140. node = attrs.getNamedItem("npcStringId");
  141. if (node != null)
  142. {
  143. npcString = NpcStringId.getNpcStringId(Integer.parseInt(node.getNodeValue()));
  144. if (npcString == null)
  145. {
  146. _log.warning(getClass().getSimpleName() + ": Unknown npcstring '" + node.getNodeValue() + ".");
  147. continue;
  148. }
  149. }
  150. }
  151. }
  152. list.add(new L2NpcWalkerNode(0, npcString, chatString, x, y, z, delay, parseBoolean(attrs, "run")));
  153. }
  154. }
  155. L2WalkRoute newRoute = new L2WalkRoute(routeId, list, repeat, false, repeatType);
  156. _routes.put(routeId, newRoute);
  157. }
  158. }
  159. }
  160. public boolean isRegistered(L2Npc npc)
  161. {
  162. return _activeRoutes.containsKey(npc.getObjectId());
  163. }
  164. public void startMoving(final L2Npc npc, final int routeId)
  165. {
  166. if (_routes.containsKey(routeId) && (npc != null) && !npc.isDead()) // check, if these route and NPC present
  167. {
  168. if (!_activeRoutes.containsKey(npc.getObjectId())) // new walk task
  169. {
  170. // only if not already moved / not engaged in battle... should not happens if called on spawn
  171. if ((npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_ACTIVE) || (npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE))
  172. {
  173. WalkInfo walk = new WalkInfo(routeId);
  174. // walk._lastActionTime = System.currentTimeMillis();
  175. L2NpcWalkerNode node = walk.getCurrentNode();
  176. if (!npc.isInsideRadius(node.getMoveX(), node.getMoveY(), node.getMoveZ(), 3000, true, false))
  177. {
  178. return;
  179. }
  180. npc.setIsRunning(node.getRunning());
  181. npc.getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new L2CharPosition(node.getMoveX(), node.getMoveY(), node.getMoveZ(), 0));
  182. walk._walkCheckTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(new Runnable()
  183. {
  184. @Override
  185. public void run()
  186. {
  187. startMoving(npc, routeId);
  188. }
  189. }, 60000, 60000); // start walk check task, for resuming walk after fight
  190. npc.getKnownList().startTrackingTask();
  191. _activeRoutes.put(npc.getObjectId(), walk); // register route
  192. }
  193. else
  194. {
  195. ThreadPoolManager.getInstance().scheduleGeneral(new Runnable()
  196. {
  197. @Override
  198. public void run()
  199. {
  200. startMoving(npc, routeId);
  201. }
  202. }, 60000);
  203. }
  204. }
  205. else
  206. // walk was stopped due to some reason (arrived to node, script action, fight or something else), resume it
  207. {
  208. if ((npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_ACTIVE) || (npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE))
  209. {
  210. WalkInfo walk = _activeRoutes.get(npc.getObjectId());
  211. // Prevent call simultaneously from scheduled task and onArrived() or temporarily stop walking for resuming in future
  212. if (walk._blocked || walk._suspended)
  213. {
  214. return;
  215. }
  216. walk._blocked = true;
  217. // Check this first, within the bounds of random moving, we have no conception of "first" or "last" node
  218. if ((walk.getRoute().getRepeatType() == REPEAT_RANDOM) && walk._nodeArrived)
  219. {
  220. int newNode = walk._currentNode;
  221. while (newNode == walk._currentNode)
  222. {
  223. newNode = Rnd.get(walk.getRoute().getNodesCount());
  224. }
  225. walk._currentNode = newNode;
  226. walk._nodeArrived = false;
  227. }
  228. else if (walk._currentNode == walk.getRoute().getNodesCount()) // Last node arrived
  229. {
  230. if (!walk.getRoute().repeatWalk())
  231. {
  232. cancelMoving(npc);
  233. return;
  234. }
  235. switch (walk.getRoute().getRepeatType())
  236. {
  237. case REPEAT_GO_BACK:
  238. walk._forward = false;
  239. walk._currentNode -= 2;
  240. break;
  241. case REPEAT_GO_FIRST:
  242. walk._currentNode = 0;
  243. break;
  244. case REPEAT_TELE_FIRST:
  245. npc.teleToLocation(npc.getSpawn().getLocx(), npc.getSpawn().getLocy(), npc.getSpawn().getLocz());
  246. walk._currentNode = 0;
  247. }
  248. }
  249. else if (walk._currentNode == -1) // First node arrived, when direction is first <-- last
  250. {
  251. walk._currentNode = 1;
  252. walk._forward = true;
  253. }
  254. L2NpcWalkerNode node = walk.getCurrentNode();
  255. npc.setIsRunning(node.getRunning());
  256. npc.getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new L2CharPosition(node.getMoveX(), node.getMoveY(), node.getMoveZ(), 0));
  257. walk._blocked = false;
  258. }
  259. }
  260. }
  261. }
  262. public synchronized void cancelMoving(L2Npc npc)
  263. {
  264. if (_activeRoutes.containsKey(npc.getObjectId()))
  265. {
  266. final WalkInfo walk = _activeRoutes.remove(npc.getObjectId());
  267. walk._walkCheckTask.cancel(true);
  268. npc.getKnownList().stopTrackingTask();
  269. }
  270. }
  271. public void resumeMoving(final L2Npc npc)
  272. {
  273. if (!_activeRoutes.containsKey(npc.getObjectId()))
  274. {
  275. return;
  276. }
  277. WalkInfo walk = _activeRoutes.get(npc.getObjectId());
  278. walk._suspended = false;
  279. startMoving(npc, walk.getRoute().getId());
  280. }
  281. public void stopMoving(L2Npc npc, boolean suspend)
  282. {
  283. if (!_activeRoutes.containsKey(npc.getObjectId()))
  284. {
  285. return;
  286. }
  287. WalkInfo walk = _activeRoutes.get(npc.getObjectId());
  288. walk._suspended = suspend;
  289. npc.stopMove(null);
  290. npc.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
  291. }
  292. public void onArrived(final L2Npc npc)
  293. {
  294. if (_activeRoutes.containsKey(npc.getObjectId()))
  295. {
  296. WalkInfo walk = _activeRoutes.get(npc.getObjectId());
  297. // Opposite should not happen... but happens sometime
  298. if ((walk._currentNode >= 0) && (walk._currentNode < walk.getRoute().getNodesCount()))
  299. {
  300. L2NpcWalkerNode node = walk.getRoute().getNodeList().get(walk._currentNode);
  301. if ((node.getMoveX() == npc.getX()) && (node.getMoveY() == npc.getY()))
  302. {
  303. walk._nodeArrived = true;
  304. if (walk.getRoute().getRepeatType() != REPEAT_RANDOM)
  305. {
  306. if (walk._forward)
  307. {
  308. walk._currentNode++;
  309. }
  310. else
  311. {
  312. walk._currentNode--;
  313. }
  314. }
  315. int delay;
  316. if (walk._currentNode >= walk.getRoute().getNodesCount())
  317. {
  318. delay = walk.getRoute().getLastNode().getDelay();
  319. }
  320. else if (walk._currentNode < 0)
  321. {
  322. delay = walk.getRoute().getNodeList().get(0).getDelay();
  323. }
  324. else
  325. {
  326. delay = walk.getCurrentNode().getDelay();
  327. }
  328. walk._blocked = true; // prevents to be ran from walk check task, if there is delay in this node.
  329. // walk._lastActionTime = System.currentTimeMillis();
  330. ThreadPoolManager.getInstance().scheduleGeneral(new ArrivedTask(npc, walk), 100 + (delay * 1000L));
  331. }
  332. }
  333. }
  334. }
  335. public void onDeath(L2Npc npc)
  336. {
  337. cancelMoving(npc);
  338. }
  339. private class ArrivedTask implements Runnable
  340. {
  341. WalkInfo _walk;
  342. L2Npc _npc;
  343. public ArrivedTask(L2Npc npc, WalkInfo walk)
  344. {
  345. _npc = npc;
  346. _walk = walk;
  347. }
  348. @Override
  349. public void run()
  350. {
  351. _walk._blocked = false;
  352. startMoving(_npc, _walk.getRoute().getId());
  353. }
  354. }
  355. public static final WalkingManager getInstance()
  356. {
  357. return SingletonHolder._instance;
  358. }
  359. private static class SingletonHolder
  360. {
  361. protected static final WalkingManager _instance = new WalkingManager();
  362. }
  363. }