/* * 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.pathfinding.cellnodes; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.logging.Level; import java.util.logging.Logger; import javolution.util.FastList; import com.l2jserver.Config; import com.l2jserver.gameserver.GeoData; import com.l2jserver.gameserver.idfactory.IdFactory; import com.l2jserver.gameserver.model.L2ItemInstance; import com.l2jserver.gameserver.model.L2World; import com.l2jserver.gameserver.pathfinding.AbstractNode; import com.l2jserver.gameserver.pathfinding.AbstractNodeLoc; import com.l2jserver.gameserver.pathfinding.PathFinding; import com.l2jserver.util.StringUtil; /** * * @author Sami, DS * Credits to Diamond */ public class CellPathFinding extends PathFinding { private static final Logger _log = Logger.getLogger(CellPathFinding.class.getName()); private BufferInfo[] _allBuffers; private int _findSuccess = 0; private int _findFails = 0; private int _postFilterUses = 0; private int _postFilterPlayableUses = 0; private int _postFilterPasses = 0; private long _postFilterElapsed = 0; private FastList _debugItems = null; public static CellPathFinding getInstance() { return SingletonHolder._instance; } private CellPathFinding() { try { String[] array = Config.PATHFIND_BUFFERS.split(";"); _allBuffers = new BufferInfo[array.length]; String buf; String[] args; for (int i = 0; i < array.length; i++) { buf = array[i]; args = buf.split("x"); if (args.length != 2) throw new Exception("Invalid buffer definition: " + buf); _allBuffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); } } catch (Exception e) { _log.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); throw new Error("CellPathFinding: load aborted"); } } /** * @see com.l2jserver.gameserver.pathfinding.PathFinding#PathNodesExist(short) */ @Override public boolean pathNodesExist(short regionoffset) { return false; } /** * @see com.l2jserver.gameserver.pathfinding.PathFinding#FindPath(int, int, short, int, int, short) */ @Override public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable) { int gx = (x - L2World.MAP_MIN_X) >> 4; int gy = (y - L2World.MAP_MIN_Y) >> 4; if (!GeoData.getInstance().hasGeo(x, y)) return null; short gz = GeoData.getInstance().getHeight(x, y, z); int gtx = (tx - L2World.MAP_MIN_X) >> 4; int gty = (ty - L2World.MAP_MIN_Y) >> 4; if (!GeoData.getInstance().hasGeo(tx, ty)) return null; short gtz = GeoData.getInstance().getHeight(tx, ty, tz); CellNodeBuffer buffer = alloc(64 + 2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty)), playable); if (buffer == null) return null; boolean debug = playable && Config.DEBUG_PATH; if (debug) { if (_debugItems == null) _debugItems = new FastList(); else { for (L2ItemInstance item : _debugItems) { if (item == null) continue; item.decayMe(); } _debugItems.clear(); } } FastList path = null; try { CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); if (debug) { for (CellNode n : buffer.debugPath()) { if (n.getCost() < 0) // calculated path dropDebugItem(1831, (int) (-n.getCost() * 10), n.getLoc()); else // known nodes dropDebugItem(57, (int) (n.getCost() * 10), n.getLoc()); } } if (result == null) { _findFails++; return null; } path = constructPath(result); } catch (Exception e) { _log.log(Level.WARNING, "", e); return null; } finally { buffer.free(); } if (path.size() < 3 || Config.MAX_POSTFILTER_PASSES <= 0) { _findSuccess++; return path; } long timeStamp = System.currentTimeMillis(); _postFilterUses++; if (playable) _postFilterPlayableUses++; int currentX, currentY, currentZ; ListIterator middlePoint, endPoint; AbstractNodeLoc locMiddle, locEnd; boolean remove; int pass = 0; do { pass++; _postFilterPasses++; remove = false; middlePoint = path.listIterator(); endPoint = path.listIterator(1); locEnd = null; currentX = x; currentY = y; currentZ = z; while (endPoint.hasNext()) { locEnd = endPoint.next(); locMiddle = middlePoint.next(); if (GeoData.getInstance().canMoveFromToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instanceId)) { middlePoint.remove(); remove = true; if (debug) dropDebugItem(735, 1, locMiddle); } else { currentX = locMiddle.getX(); currentY = locMiddle.getY(); currentZ = locMiddle.getZ(); } } } // only one postfilter pass for AI while (playable && remove && path.size() > 2 && pass < Config.MAX_POSTFILTER_PASSES); if (debug) { middlePoint = path.listIterator(); while (middlePoint.hasNext()) { locMiddle = middlePoint.next(); dropDebugItem(65, 1, locMiddle); } } _findSuccess++; _postFilterElapsed += System.currentTimeMillis() - timeStamp; return path; } private FastList constructPath(AbstractNode node) { FastList path = new FastList(); int previousDirectionX = Integer.MIN_VALUE; int previousDirectionY = Integer.MIN_VALUE; int directionX, directionY; while (node.getParent() != null) { if (!Config.ADVANCED_DIAGONAL_STRATEGY && node.getParent().getParent() != null) { int tmpX = node.getLoc().getNodeX() - node.getParent().getParent().getLoc().getNodeX(); int tmpY = node.getLoc().getNodeY() - node.getParent().getParent().getLoc().getNodeY(); if (Math.abs(tmpX) == Math.abs(tmpY)) { directionX = tmpX; directionY = tmpY; } else { directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); } } else { directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); } // only add a new route point if moving direction changes if (directionX != previousDirectionX || directionY != previousDirectionY) { previousDirectionX = directionX; previousDirectionY = directionY; path.addFirst(node.getLoc()); node.setLoc(null); } node = node.getParent(); } return path; } private final CellNodeBuffer alloc(int size, boolean playable) { CellNodeBuffer current = null; for (BufferInfo i : _allBuffers) { if (i.mapSize >= size) { for (CellNodeBuffer buf : i.bufs) { if (buf.lock()) { i.uses++; if (playable) i.playableUses++; i.elapsed += buf.getElapsedTime(); current = buf; break; } } if (current != null) break; // not found, allocate temporary buffer current = new CellNodeBuffer(i.mapSize); current.lock(); if (i.bufs.size() < i.count) { i.bufs.add(current); i.uses++; if (playable) i.playableUses++; break; } i.overflows++; if (playable) i.playableOverflows++; //System.err.println("Overflow, size requested: " + size + " playable:"+playable); } } return current; } private final void dropDebugItem(int itemId, int num, AbstractNodeLoc loc) { final L2ItemInstance item = new L2ItemInstance(IdFactory.getInstance().getNextId(), itemId); item.setCount(num); item.spawnMe(loc.getX(), loc.getY(), loc.getZ()); _debugItems.add(item); } private static final class BufferInfo { final int mapSize; final int count; ArrayList bufs; int uses = 0; int playableUses = 0; int overflows = 0; int playableOverflows = 0; long elapsed = 0; public BufferInfo(int size, int cnt) { mapSize = size; count = cnt; bufs = new ArrayList(count); } @Override public String toString() { final StringBuilder stat = new StringBuilder(100); StringUtil.append(stat, String.valueOf(mapSize), "x", String.valueOf(mapSize), " num:", String.valueOf(bufs.size()), "/" ,String.valueOf(count), " uses:", String.valueOf(uses), "/", String.valueOf(playableUses)); if (uses > 0) StringUtil.append(stat, " total/avg(ms):", String.valueOf(elapsed), "/", String.format("%1.2f", (double)elapsed/uses)); StringUtil.append(stat, " ovf:", String.valueOf(overflows), "/", String.valueOf(playableOverflows)); return stat.toString(); } } @Override public String[] getStat() { final String[] result = new String[_allBuffers.length + 1]; for (int i = 0; i < _allBuffers.length; i++) result[i] = _allBuffers[i].toString(); final StringBuilder stat = new StringBuilder(100); StringUtil.append(stat, "LOS postfilter uses:", String.valueOf(_postFilterUses), "/", String.valueOf(_postFilterPlayableUses)); if (_postFilterUses > 0) StringUtil.append(stat, " total/avg(ms):", String.valueOf(_postFilterElapsed), "/", String.format("%1.2f", (double)_postFilterElapsed/_postFilterUses), " passes total/avg:", String.valueOf(_postFilterPasses), "/", String.format("%1.1f", (double)_postFilterPasses/_postFilterUses),"\r\n"); StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails)); result[result.length - 1] = stat.toString(); return result; } @SuppressWarnings("synthetic-access") private static class SingletonHolder { protected static final CellPathFinding _instance = new CellPathFinding(); } }