/* * This program 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. * * This program 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 . */ package com.l2jserver.gameserver.instancemanager; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledFuture; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import com.l2jserver.gameserver.ThreadPoolManager; import com.l2jserver.gameserver.ai.CtrlIntention; 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.actor.L2Npc; import com.l2jserver.gameserver.network.NpcStringId; import com.l2jserver.util.Rnd; /** * This class manages walking monsters. * @author GKR */ public class WalkingManager extends DocumentParser { //Repeat style: 0 - go back, 1 - go to first point (circle style), 2 - teleport to first point (conveyor style), 3 - random walking between points. private static final byte REPEAT_GO_BACK = 0; 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 _routes = new HashMap<>(); //all available routes private Map _activeRoutes = new HashMap<>(); //each record represents NPC, moving by predefined route from _routes, and moving progress private class WalkInfo { protected ScheduledFuture _walkCheckTask; protected boolean _blocked = false; protected boolean _suspended = false; protected boolean _nodeArrived = false; protected int _currentNode = 0; protected boolean _forward = true; //Determines first --> last or first <-- last direction private int _routeId; public WalkInfo(int routeId) { _routeId = routeId; } protected L2WalkRoute getRoute() { return _routes.get(_routeId); } protected L2NpcWalkerNode getCurrentNode() { return getRoute().getNodeList().get(_currentNode); } } 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() { Node n = getCurrentDocument().getFirstChild(); for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) { if (d.getNodeName().equals("route")) { final Integer routeId = parseInteger(d.getAttributes(), "id"); boolean repeat = parseBoolean(d.getAttributes(), "repeat"); String repeatStyle = d.getAttributes().getNamedItem("repeatStyle").getNodeValue(); byte repeatType; if (repeatStyle.equalsIgnoreCase("back")) repeatType = REPEAT_GO_BACK; else if (repeatStyle.equalsIgnoreCase("cycle")) repeatType = REPEAT_GO_FIRST; else if (repeatStyle.equalsIgnoreCase("conveyor")) repeatType = REPEAT_TELE_FIRST; else if (repeatStyle.equalsIgnoreCase("random")) repeatType = REPEAT_RANDOM; else repeatType = -1; final List list = new ArrayList<>(); for (Node r = d.getFirstChild(); r != null; r = r.getNextSibling()) { if (r.getNodeName().equals("point")) { NamedNodeMap attrs = r.getAttributes(); int x = parseInt(attrs, "X"); 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"); if (node != null) chatString = node.getNodeValue(); else { node = attrs.getNamedItem("npcString"); if (node != null) { npcString = NpcStringId.getNpcStringId(node.getNodeValue()); if (npcString == null) { _log.warning(getClass().getSimpleName() + ": Unknown npcstring '" + node.getNodeValue() + "."); continue; } } else { node = attrs.getNamedItem("npcStringId"); if (node != null) { npcString = NpcStringId.getNpcStringId(Integer.parseInt(node.getNodeValue())); if (npcString == null) { _log.warning(getClass().getSimpleName() + ": Unknown npcstring '" + node.getNodeValue() + "."); continue; } } } } list.add(new L2NpcWalkerNode(0, npcString, chatString, x, y, z, delay, parseBoolean(attrs, "run"))); } } L2WalkRoute newRoute = new L2WalkRoute(routeId, list, repeat, false, repeatType); _routes.put(routeId, newRoute); } } } public boolean isRegistered(L2Npc npc) { return _activeRoutes.containsKey(npc.getObjectId()); } public void startMoving(final L2Npc npc, final int routeId) { if (_routes.containsKey(routeId) && npc != null && !npc.isDead()) //check, if these route and NPC present { if (!_activeRoutes.containsKey(npc.getObjectId())) //new walk task { //only if not already moved / not engaged in battle... should not happens if called on spawn if (npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_ACTIVE || npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE) { WalkInfo walk = new WalkInfo(routeId); //walk._lastActionTime = System.currentTimeMillis(); L2NpcWalkerNode node = walk.getCurrentNode(); if (!npc.isInsideRadius(node.getMoveX(), node.getMoveY(), node.getMoveZ(), 3000, true, false)) //too far from first point, decline further operations return; 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() { @Override public void run() { 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 //try a bit later ThreadPoolManager.getInstance().scheduleGeneral(new Runnable() { @Override public void run() { startMoving(npc, routeId); } }, 60000); } else //walk was stopped due to some reason (arrived to node, script action, fight or something else), resume it { 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) 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.setIsRunning(node.getRunning()); npc.getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new L2CharPosition(node.getMoveX(), node.getMoveY(), node.getMoveZ(), 0)); walk._blocked = false; } } } } public synchronized void cancelMoving(L2Npc npc) { if (_activeRoutes.containsKey(npc.getObjectId())) { final WalkInfo walk = _activeRoutes.remove(npc.getObjectId()); walk._walkCheckTask.cancel(true); npc.getKnownList().stopTrackingTask(); } } public void resumeMoving(final L2Npc npc) { if (!_activeRoutes.containsKey(npc.getObjectId())) return; WalkInfo walk = _activeRoutes.get(npc.getObjectId()); walk._suspended = false; startMoving(npc, walk.getRoute().getId()); } public void stopMoving(L2Npc npc, boolean suspend) { if (!_activeRoutes.containsKey(npc.getObjectId())) return; WalkInfo walk = _activeRoutes.get(npc.getObjectId()); walk._suspended = suspend; npc.stopMove(null); npc.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE); } public void onArrived(final L2Npc npc) { if (_activeRoutes.containsKey(npc.getObjectId())) { 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 delay = walk.getCurrentNode().getDelay(); 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); } } } } public void onDeath(L2Npc npc) { cancelMoving(npc); } private class ArrivedTask implements Runnable { WalkInfo _walk; L2Npc _npc; public ArrivedTask(L2Npc npc, WalkInfo walk) { _npc = npc; _walk = walk; } @Override public void run() { _walk._blocked = false; startMoving(_npc,_walk. getRoute().getId()); } } public static final WalkingManager getInstance() { return SingletonHolder._instance; } private static class SingletonHolder { protected static final WalkingManager _instance = new WalkingManager(); } }