123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467 |
- /*
- * Copyright (C) 2004-2014 L2J Server
- *
- * This file is part of L2J Server.
- *
- * L2J Server 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.
- *
- * L2J Server 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 <http://www.gnu.org/licenses/>.
- */
- package com.l2jserver.gameserver.pathfinding.geonodes;
- import java.io.BufferedReader;
- import java.io.File;
- import java.io.FileReader;
- import java.io.LineNumberReader;
- import java.io.RandomAccessFile;
- import java.nio.ByteBuffer;
- import java.nio.IntBuffer;
- import java.nio.MappedByteBuffer;
- import java.nio.channels.FileChannel;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Map;
- import java.util.StringTokenizer;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import javolution.util.FastList;
- import javolution.util.FastMap;
- import com.l2jserver.Config;
- import com.l2jserver.gameserver.GeoData;
- import com.l2jserver.gameserver.model.L2World;
- import com.l2jserver.gameserver.model.Location;
- import com.l2jserver.gameserver.pathfinding.AbstractNode;
- import com.l2jserver.gameserver.pathfinding.AbstractNodeLoc;
- import com.l2jserver.gameserver.pathfinding.PathFinding;
- import com.l2jserver.gameserver.pathfinding.utils.FastNodeList;
- /**
- * @author -Nemesiss-
- */
- public class GeoPathFinding extends PathFinding
- {
- private static Logger _log = Logger.getLogger(GeoPathFinding.class.getName());
- private static Map<Short, ByteBuffer> _pathNodes = new FastMap<>();
- private static Map<Short, IntBuffer> _pathNodesIndex = new FastMap<>();
-
- public static GeoPathFinding getInstance()
- {
- return SingletonHolder._instance;
- }
-
- @Override
- public boolean pathNodesExist(short regionoffset)
- {
- return _pathNodesIndex.containsKey(regionoffset);
- }
-
- @Override
- public List<AbstractNodeLoc> 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;
- short gz = (short) z;
- int gtx = (tx - L2World.MAP_MIN_X) >> 4;
- int gty = (ty - L2World.MAP_MIN_Y) >> 4;
- short gtz = (short) tz;
-
- GeoNode start = readNode(gx, gy, gz);
- GeoNode end = readNode(gtx, gty, gtz);
- if ((start == null) || (end == null))
- {
- return null;
- }
- if (Math.abs(start.getLoc().getZ() - z) > 55)
- {
- return null; // not correct layer
- }
- if (Math.abs(end.getLoc().getZ() - tz) > 55)
- {
- return null; // not correct layer
- }
- if (start == end)
- {
- return null;
- }
-
- // TODO: Find closest path node we CAN access. Now only checks if we can not reach the closest
- Location temp = GeoData.getInstance().moveCheck(x, y, z, start.getLoc().getX(), start.getLoc().getY(), start.getLoc().getZ(), instanceId);
- if ((temp.getX() != start.getLoc().getX()) || (temp.getY() != start.getLoc().getY()))
- {
- return null; // cannot reach closest...
- }
-
- // TODO: Find closest path node around target, now only checks if final location can be reached
- temp = GeoData.getInstance().moveCheck(tx, ty, tz, end.getLoc().getX(), end.getLoc().getY(), end.getLoc().getZ(), instanceId);
- if ((temp.getX() != end.getLoc().getX()) || (temp.getY() != end.getLoc().getY()))
- {
- return null; // cannot reach closest...
- }
-
- // return searchAStar(start, end);
- return searchByClosest2(start, end);
- }
-
- public List<AbstractNodeLoc> searchByClosest2(GeoNode start, GeoNode end)
- {
- // Always continues checking from the closest to target non-blocked
- // node from to_visit list. There's extra length in path if needed
- // to go backwards/sideways but when moving generally forwards, this is extra fast
- // and accurate. And can reach insane distances (try it with 800 nodes..).
- // Minimum required node count would be around 300-400.
- // Generally returns a bit (only a bit) more intelligent looking routes than
- // the basic version. Not a true distance image (which would increase CPU
- // load) level of intelligence though.
-
- // List of Visited Nodes
- FastNodeList visited = new FastNodeList(550);
-
- // List of Nodes to Visit
- LinkedList<GeoNode> to_visit = new LinkedList<>();
- to_visit.add(start);
- int targetX = end.getLoc().getNodeX();
- int targetY = end.getLoc().getNodeY();
-
- int dx, dy;
- boolean added;
- int i = 0;
- while (i < 550)
- {
- GeoNode node;
- try
- {
- node = to_visit.removeFirst();
- }
- catch (Exception e)
- {
- // No Path found
- return null;
- }
- if (node.equals(end))
- {
- return constructPath2(node);
- }
-
- i++;
- visited.add(node);
- node.attachNeighbors();
- GeoNode[] neighbors = node.getNeighbors();
- if (neighbors == null)
- {
- continue;
- }
- for (GeoNode n : neighbors)
- {
- if (!visited.containsRev(n) && !to_visit.contains(n))
- {
- added = false;
- n.setParent(node);
- dx = targetX - n.getLoc().getNodeX();
- dy = targetY - n.getLoc().getNodeY();
- n.setCost((dx * dx) + (dy * dy));
- for (int index = 0; index < to_visit.size(); index++)
- {
- // supposed to find it quite early..
- if (to_visit.get(index).getCost() > n.getCost())
- {
- to_visit.add(index, n);
- added = true;
- break;
- }
- }
- if (!added)
- {
- to_visit.addLast(n);
- }
- }
- }
- }
- // No Path found
- return null;
- }
-
- public List<AbstractNodeLoc> constructPath2(AbstractNode node)
- {
- LinkedList<AbstractNodeLoc> path = new LinkedList<>();
- int previousDirectionX = -1000;
- int previousDirectionY = -1000;
- int directionX;
- int directionY;
-
- while (node.getParent() != null)
- {
- // only add a new route point if moving direction changes
- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX();
- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY();
-
- if ((directionX != previousDirectionX) || (directionY != previousDirectionY))
- {
- previousDirectionX = directionX;
- previousDirectionY = directionY;
- path.addFirst(node.getLoc());
- }
- node = node.getParent();
- }
- return path;
- }
-
- public GeoNode[] readNeighbors(GeoNode n, int idx)
- {
- int node_x = n.getLoc().getNodeX();
- int node_y = n.getLoc().getNodeY();
- // short node_z = n.getLoc().getZ();
-
- short regoffset = getRegionOffset(getRegionX(node_x), getRegionY(node_y));
- ByteBuffer pn = _pathNodes.get(regoffset);
-
- List<AbstractNode> Neighbors = new FastList<>(8);
- GeoNode newNode;
- short new_node_x, new_node_y;
-
- // Region for sure will change, we must read from correct file
- byte neighbor = pn.get(idx++); // N
- if (neighbor > 0)
- {
- neighbor--;
- new_node_x = (short) node_x;
- new_node_y = (short) (node_y - 1);
- newNode = readNode(new_node_x, new_node_y, neighbor);
- if (newNode != null)
- {
- Neighbors.add(newNode);
- }
- }
- neighbor = pn.get(idx++); // NE
- if (neighbor > 0)
- {
- neighbor--;
- new_node_x = (short) (node_x + 1);
- new_node_y = (short) (node_y - 1);
- newNode = readNode(new_node_x, new_node_y, neighbor);
- if (newNode != null)
- {
- Neighbors.add(newNode);
- }
- }
- neighbor = pn.get(idx++); // E
- if (neighbor > 0)
- {
- neighbor--;
- new_node_x = (short) (node_x + 1);
- new_node_y = (short) node_y;
- newNode = readNode(new_node_x, new_node_y, neighbor);
- if (newNode != null)
- {
- Neighbors.add(newNode);
- }
- }
- neighbor = pn.get(idx++); // SE
- if (neighbor > 0)
- {
- neighbor--;
- new_node_x = (short) (node_x + 1);
- new_node_y = (short) (node_y + 1);
- newNode = readNode(new_node_x, new_node_y, neighbor);
- if (newNode != null)
- {
- Neighbors.add(newNode);
- }
- }
- neighbor = pn.get(idx++); // S
- if (neighbor > 0)
- {
- neighbor--;
- new_node_x = (short) node_x;
- new_node_y = (short) (node_y + 1);
- newNode = readNode(new_node_x, new_node_y, neighbor);
- if (newNode != null)
- {
- Neighbors.add(newNode);
- }
- }
- neighbor = pn.get(idx++); // SW
- if (neighbor > 0)
- {
- neighbor--;
- new_node_x = (short) (node_x - 1);
- new_node_y = (short) (node_y + 1);
- newNode = readNode(new_node_x, new_node_y, neighbor);
- if (newNode != null)
- {
- Neighbors.add(newNode);
- }
- }
- neighbor = pn.get(idx++); // W
- if (neighbor > 0)
- {
- neighbor--;
- new_node_x = (short) (node_x - 1);
- new_node_y = (short) node_y;
- newNode = readNode(new_node_x, new_node_y, neighbor);
- if (newNode != null)
- {
- Neighbors.add(newNode);
- }
- }
- neighbor = pn.get(idx++); // NW
- if (neighbor > 0)
- {
- neighbor--;
- new_node_x = (short) (node_x - 1);
- new_node_y = (short) (node_y - 1);
- newNode = readNode(new_node_x, new_node_y, neighbor);
- if (newNode != null)
- {
- Neighbors.add(newNode);
- }
- }
- GeoNode[] result = new GeoNode[Neighbors.size()];
- return Neighbors.toArray(result);
- }
-
- // Private
-
- private GeoNode readNode(short node_x, short node_y, byte layer)
- {
- short regoffset = getRegionOffset(getRegionX(node_x), getRegionY(node_y));
- if (!pathNodesExist(regoffset))
- {
- return null;
- }
- short nbx = getNodeBlock(node_x);
- short nby = getNodeBlock(node_y);
- int idx = _pathNodesIndex.get(regoffset).get((nby << 8) + nbx);
- ByteBuffer pn = _pathNodes.get(regoffset);
- // reading
- byte nodes = pn.get(idx);
- idx += (layer * 10) + 1;// byte + layer*10byte
- if (nodes < layer)
- {
- _log.warning("SmthWrong!");
- }
- short node_z = pn.getShort(idx);
- idx += 2;
- return new GeoNode(new GeoNodeLoc(node_x, node_y, node_z), idx);
- }
-
- private GeoNode readNode(int gx, int gy, short z)
- {
- short node_x = getNodePos(gx);
- short node_y = getNodePos(gy);
- short regoffset = getRegionOffset(getRegionX(node_x), getRegionY(node_y));
- if (!pathNodesExist(regoffset))
- {
- return null;
- }
- short nbx = getNodeBlock(node_x);
- short nby = getNodeBlock(node_y);
- int idx = _pathNodesIndex.get(regoffset).get((nby << 8) + nbx);
- ByteBuffer pn = _pathNodes.get(regoffset);
- // reading
- byte nodes = pn.get(idx++);
- int idx2 = 0; // create index to nearlest node by z
- short last_z = Short.MIN_VALUE;
- while (nodes > 0)
- {
- short node_z = pn.getShort(idx);
- if (Math.abs(last_z - z) > Math.abs(node_z - z))
- {
- last_z = node_z;
- idx2 = idx + 2;
- }
- idx += 10; // short + 8 byte
- nodes--;
- }
- return new GeoNode(new GeoNodeLoc(node_x, node_y, last_z), idx2);
- }
-
- protected GeoPathFinding()
- {
- final File file = new File(Config.PATHNODE_DIR, "pn_index.txt");
- try (FileReader fr = new FileReader(file);
- BufferedReader br = new BufferedReader(fr);
- LineNumberReader lnr = new LineNumberReader(br))
- {
- _log.info("Path Engine: - Loading Path Nodes...");
- String line;
- while ((line = lnr.readLine()) != null)
- {
- if (line.trim().isEmpty())
- {
- continue;
- }
- StringTokenizer st = new StringTokenizer(line, "_");
- byte rx = Byte.parseByte(st.nextToken());
- byte ry = Byte.parseByte(st.nextToken());
- LoadPathNodeFile(rx, ry);
- }
- }
- catch (Exception e)
- {
- _log.log(Level.WARNING, "", e);
- throw new Error("Failed to Read pn_index File.");
- }
- }
-
- private void LoadPathNodeFile(byte rx, byte ry)
- {
- if ((rx < L2World.TILE_X_MIN) || (rx > L2World.TILE_X_MAX) || (ry < L2World.TILE_Y_MIN) || (ry > L2World.TILE_Y_MAX))
- {
- _log.warning("Failed to Load PathNode File: invalid region " + rx + "," + ry + Config.EOL);
- return;
- }
- short regionoffset = getRegionOffset(rx, ry);
- File file = new File(Config.PATHNODE_DIR, rx + "_" + ry + ".pn");
- _log.info("Path Engine: - Loading: " + file.getName() + " -> region offset: " + regionoffset + " X: " + rx + " Y: " + ry);
- int node = 0, size, index = 0;
-
- // Create a read-only memory-mapped file
- try (RandomAccessFile raf = new RandomAccessFile(file, "r");
- FileChannel roChannel = raf.getChannel())
- {
- size = (int) roChannel.size();
- MappedByteBuffer nodes;
- if (Config.FORCE_GEODATA)
- {
- // it is not guarantee, because the underlying operating system may have paged out some of the buffer's data
- nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load();
- }
- else
- {
- nodes = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
- }
-
- // Indexing pathnode files, so we will know where each block starts
- IntBuffer indexs = IntBuffer.allocate(65536);
-
- while (node < 65536)
- {
- byte layer = nodes.get(index);
- indexs.put(node++, index);
- index += (layer * 10) + 1;
- }
- _pathNodesIndex.put(regionoffset, indexs);
- _pathNodes.put(regionoffset, nodes);
- }
- catch (Exception e)
- {
- _log.log(Level.WARNING, "Failed to Load PathNode File: " + file.getAbsolutePath() + " : " + e.getMessage(), e);
- }
- }
-
- private static class SingletonHolder
- {
- protected static final GeoPathFinding _instance = new GeoPathFinding();
- }
- }
|